OpenCores
URL https://opencores.org/ocsvn/forth-cpu/forth-cpu/trunk

Subversion Repositories forth-cpu

[/] [forth-cpu/] [trunk/] [util.vhd] - Rev 5

Compare with Previous | Blame | View Log

-------------------------------------------------------------------------------
--| @file util.vhd
--| @brief A collection of utilities and simple components. The components
--| should be synthesizable, and the functions can be used within synthesizable
--| components, unless marked with a "_tb" suffix (or if the function n_bits).
--|
--| Other modules to implement; CRC core (reuse LFSR), Count
--| Leading Zeros, Count Trailing Zeros, Manchester CODEC, Wishbone interface
--| types and Wishbone Bus Arbitrator. Also SPI, a H2 core with an eForth image
--| read to go, and UART.
--|
--| More exotic modules would include; encryption, compression, sorting networks,
--| switching networks, Reed-Solomon CODEC, Discrete Fourier Transform/Discrete
--| Cosine Transform, Pulse Width/Code/Position Modulation modules, so long as
--| they are fairly generic and synthesizable.
--|
--| Potential improvements to the library:
--| - Optional registers on either input or output, selectable by a generic
--| - Better timing models
--| - More assertions
--| - See 'A Structured VHDL design' by Jiri Gaisler,
--|   <http://gaisler.com/doc/vhdl2proc.pdf> and apply methodology.
--|
--| @author         Richard James Howe
--| @copyright      Copyright 2017, 2019 Richard James Howe
--| @license        MIT
--| @email          howe.r.j.89@gmail.com
-------------------------------------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
 
package util is
	-- Not all modules will need every generic specified here, even so it
	-- is easier to group the common generics in one structure.
	type common_generics is record
		clock_frequency:    positive; -- clock frequency of module clock
		delay:              time;     -- gate delay for simulation purposes
		asynchronous_reset: boolean;  -- use asynchronous reset if true
	end record;
 
	constant default_settings: common_generics := (
		clock_frequency    => 100_000_000,
		delay              => 10 ns,
		asynchronous_reset => true
	);
 
	component util_tb is
		generic (g: common_generics);
	end component;
 
	component clock_source_tb is
		generic (g: common_generics; hold_rst: positive := 1);
		port (
			stop:            in     std_ulogic := '0';
			clk:             out    std_ulogic;
			clk_with_jitter: out    std_ulogic := '0';
			rst:             out    std_ulogic := '0');
	end component;
 
	component reg
		generic (g: common_generics; N: positive);
		port (
			clk: in  std_ulogic;
			rst: in  std_ulogic;
			we:  in  std_ulogic;
			di:  in  std_ulogic_vector(N - 1 downto 0);
			do:  out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component shift_register
		generic (g: common_generics; N: positive);
		port (
			clk:     in  std_ulogic;
			rst:     in  std_ulogic;
			we:      in  std_ulogic;
			di:      in  std_ulogic;
			do:      out std_ulogic;
 
			-- optional
			load_we: in  std_ulogic := '0';
			load_i:  in  std_ulogic_vector(N - 1 downto 0) := (others => '0');
			load_o:  out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component shift_register_tb
		generic (g: common_generics);
	end component;
 
	component timer_us
		generic (g: common_generics; timer_period_us: natural);
		port (
			clk: in  std_ulogic;
			rst: in  std_ulogic;
			co:  out std_ulogic);
	end component;
 
	component timer_us_tb
		generic (g: common_generics);
	end component;
 
	component rising_edge_detector is
		generic (g: common_generics);
		port (
			clk:    in  std_ulogic;
			rst:    in  std_ulogic;
			di:     in  std_ulogic;
			do:     out std_ulogic);
	end component;
 
	component rising_edge_detector_tb is
		generic (g: common_generics);
	end component;
 
	component rising_edge_detectors is
		generic (g: common_generics; N: positive);
		port (
			clk:    in  std_ulogic;
			rst:    in  std_ulogic;
			di:     in  std_ulogic_vector(N - 1 downto 0);
			do:     out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	-- NB. 'half_adder' test bench is folded in to 'full_adder_tb'
	component half_adder is
		generic (g: common_generics); -- simulation only
		port (
			a:     in  std_ulogic;
			b:     in  std_ulogic;
			sum:   out std_ulogic;
			carry: out std_ulogic);
	end component;
 
	component full_adder is
		generic (g: common_generics); -- simulation only
		port (
			x:     in    std_ulogic;
			y:     in    std_ulogic;
			z:     in    std_ulogic;
			sum:   out   std_ulogic;
			carry: out   std_ulogic);
	end component;
 
	component full_adder_tb is
		generic (g: common_generics);
	end component;
 
	component fifo is
		generic (g: common_generics;
			data_width:  positive;
			fifo_depth:  positive;
			read_first:  boolean := true);
		port (
			clk:   in  std_ulogic;
			rst:   in  std_ulogic;
			di:    in  std_ulogic_vector(data_width - 1 downto 0);
			we:    in  std_ulogic;
			re:    in  std_ulogic;
			do:    out std_ulogic_vector(data_width - 1 downto 0);
 
			-- optional
			full:  out std_ulogic := '0';
			empty: out std_ulogic := '1');
	end component;
 
	component fifo_tb is
		generic (g: common_generics);
	end component;
 
	component counter is
		generic (g: common_generics; N: positive);
		port (
			clk:     in  std_ulogic;
			rst:     in  std_ulogic;
			ce:      in  std_ulogic;
			cr:      in  std_ulogic;
			dout:    out std_ulogic_vector(N - 1 downto 0);
 
			-- optional
			load_we: in  std_ulogic := '0';
			load_i:  in  std_ulogic_vector(N - 1 downto 0) := (others => '0'));
	end component;
 
	component counter_tb is
		generic (g: common_generics);
	end component;
 
	component lfsr is
		generic (g: common_generics; constant tap: std_ulogic_vector);
		port (
			clk: in  std_ulogic;
			rst: in  std_ulogic;
			ce:  in  std_ulogic := '1';
			we:  in  std_ulogic;
			di:  in  std_ulogic_vector(tap'high + 1 downto tap'low);
			do:  out std_ulogic_vector(tap'high + 1 downto tap'low));
	end component;
 
	component lfsr_tb is
		generic (g: common_generics);
	end component;
 
	component io_pins is
		generic (g: common_generics; N: positive);
		port (
			clk:         in    std_ulogic;
			rst:         in    std_ulogic;
			control:     in    std_ulogic_vector(N - 1 downto 0);
			control_we:  in    std_ulogic;
			din:         in    std_ulogic_vector(N - 1 downto 0);
			din_we:      in    std_ulogic;
			dout:        out   std_ulogic_vector(N - 1 downto 0);
			pins:        inout std_logic_vector(N - 1 downto 0)); -- NB!
	end component;
 
	component io_pins_tb is
		generic (g: common_generics);
	end component;
 
	type file_format is (FILE_HEX, FILE_BINARY, FILE_NONE);
 
	component dual_port_block_ram is
	generic (g: common_generics;
		addr_length: positive    := 12;
		data_length: positive    := 16;
		file_name:   string      := "memory.bin";
		file_type:   file_format := FILE_BINARY);
	port (
		-- port A of dual port RAM
		a_clk:  in  std_ulogic;
		a_dwe:  in  std_ulogic;
		a_dre:  in  std_ulogic;
		a_addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		a_din:  in  std_ulogic_vector(data_length - 1 downto 0);
		a_dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0');
		-- port B of dual port RAM
		b_clk:  in  std_ulogic;
		b_dwe:  in  std_ulogic;
		b_dre:  in  std_ulogic;
		b_addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		b_din:  in  std_ulogic_vector(data_length - 1 downto 0);
		b_dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0'));
	end component;
 
	component single_port_block_ram is
	generic (g: common_generics;
		addr_length: positive    := 12;
		data_length: positive    := 16;
		file_name:   string      := "memory.bin";
		file_type:   file_format := FILE_BINARY);
	port (
		clk:  in  std_ulogic;
		dwe:  in  std_ulogic;
		dre:  in  std_ulogic;
		addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		din:  in  std_ulogic_vector(data_length - 1 downto 0);
		dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0'));
	end component;
 
	component data_source is
		generic (g: common_generics;
			addr_length: positive    := 12;
			data_length: positive    := 16;
			file_name:   string      := "memory.bin";
			file_type:   file_format := FILE_BINARY);
		port (
			clk:     in  std_ulogic;
			rst:     in  std_ulogic;
 
			ce:      in  std_ulogic := '1';
			cr:      in  std_ulogic;
 
			load:    in  std_ulogic_vector(addr_length - 1 downto 0) := (others => '0');
			load_we: in  std_ulogic := '0';
 
			dout:    out std_ulogic_vector(data_length - 1 downto 0));
	end component;
 
	component ucpu is
		generic (
			asynchronous_reset:  boolean := true; -- use asynchronous reset if true, synchronous if false
			delay:               time    := 0 ns; -- simulation only
 
			width:               positive range 8 to 32 := 8);
		port (
			clk, rst: in  std_ulogic;
 
			pc:       out std_ulogic_vector(width - 3 downto 0);
			op:       in  std_ulogic_vector(width - 1 downto 0);
 
			adr:      out std_ulogic_vector(width - 3 downto 0);
			di:       in  std_ulogic_vector(width - 1 downto 0);
			re, we:   out std_ulogic;
			do:       out std_ulogic_vector(width - 1 downto 0));
	end component;
 
	component ucpu_tb is
		generic (g: common_generics; file_name: string := "ucpu.bin");
	end component;
 
	component restoring_divider is
		generic (g: common_generics; N: positive);
		port (
			clk:   in  std_ulogic;
			rst:   in  std_ulogic := '0';
 
			a:     in  std_ulogic_vector(N - 1 downto 0);
			b:     in  std_ulogic_vector(N - 1 downto 0);
			start: in  std_ulogic;
			done:  out std_ulogic;
			c:     out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component restoring_divider_tb is
		generic (g: common_generics);
	end component;
 
	component debounce_us is
		generic (g: common_generics; timer_period_us: natural);
		port (
			clk:   in  std_ulogic;
			di:    in  std_ulogic;
			do:    out std_ulogic);
	end component;
 
	component debounce_block_us is
		generic (g: common_generics; N: positive; timer_period_us: natural);
		port (
			clk:   in  std_ulogic;
			di:    in  std_ulogic_vector(N - 1 downto 0);
			do:    out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component debounce_us_tb is
		generic (g: common_generics);
	end component;
 
	component state_changed is
		generic (g: common_generics);
		port (
			clk: in  std_ulogic;
			rst: in  std_ulogic;
			di:  in  std_ulogic;
			do:  out std_ulogic);
	end component;
 
	component state_block_changed is
		generic (g: common_generics; N: positive);
		port (
			clk: in  std_ulogic;
			rst: in  std_ulogic;
			di:  in  std_ulogic_vector(N - 1 downto 0);
			do:  out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component reset_generator is
		generic (g: common_generics; reset_period_us: natural := 1);
		port (
			clk: in  std_logic := 'X';
			rst: out std_logic := '0'); -- reset out!
	end component;
 
	component reset_generator_tb is
		generic (g: common_generics);
	end component;
 
	function n_bits(x: natural) return natural;           -- Not synthesizable
	function n_bits(x: std_ulogic_vector) return natural; -- Not synthesizable
 
	component bit_count is
		generic (g: common_generics; N: positive);
		port (
			bits:   in std_ulogic_vector(N - 1 downto 0);
			count: out std_ulogic_vector(n_bits(N) downto 0));
	end component;
 
	component bit_count_tb is
		generic (g: common_generics);
	end component;
 
	component majority is
		generic (g: common_generics; N: positive; even_wins: boolean := false);
		port (
			bits: in std_ulogic_vector(N - 1 downto 0);
			vote: out std_ulogic;
			tie: out std_ulogic);
	end component;
 
	component majority_tb is
		generic (g: common_generics);
	end component;
 
	component delay_line is
		generic (g: common_generics; width: positive; depth: natural);
		port (
			clk: in std_ulogic;
			rst: in std_ulogic;
			ce:  in std_ulogic := '1';
			di:  in std_ulogic_vector(width - 1 downto 0);
			do: out std_ulogic_vector(width - 1 downto 0));
	end component;
 
	component delay_line_tb is
		generic (g: common_generics);
	end component;
 
	component gray_encoder is
		generic (g: common_generics; N: positive);
		port (di: in std_ulogic_vector(N - 1 downto 0);
		     do: out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component gray_decoder is
		generic (g: common_generics; N: positive);
		port (di: in std_ulogic_vector(N - 1 downto 0);
		     do: out std_ulogic_vector(N - 1 downto 0));
	end component;
 
	component gray_tb is
		generic (g: common_generics);
	end component;
 
	component parity_module is
		generic (g: common_generics; N: positive; even: boolean);
		port (di: in std_ulogic_vector(N - 1 downto 0);
			do: out std_ulogic);
	end component;
 
	component hamming_7_4_encoder is
		generic (g: common_generics);
		port (
			di:      in std_ulogic_vector(3 downto 0);
			do:     out std_ulogic_vector(6 downto 0);
			parity: out std_ulogic -- parity over 'di'
		);
	end component;
 
	component hamming_7_4_decoder is
		generic (g: common_generics; secdec: boolean := true);
		port (
			di:      in std_ulogic_vector(6 downto 0);
			parity:  in std_ulogic;
			do:     out std_ulogic_vector(3 downto 0);
			single, double: out std_ulogic);
	end component;
 
	component hamming_7_4_tb is
		generic (g: common_generics);
	end component;
 
	type vga_configuration is record
		clock_frequency: positive;   -- pixel clock frequency
		h_pulse:         integer;    -- horizontal sync pulse width in pixels
		h_back_porch:    integer;    -- horizontal back porch width in pixels
		h_pixels:        integer;    -- horizontal display width in pixels
		h_front_porch:   integer;    -- horizontal front porch width in pixels
		h_polarity:      std_ulogic; -- horizontal sync pulse polarity (1 = positive, 0 = negative)
 
		v_pulse:         integer;    -- vertical sync pulse width in rows
		v_back_porch:    integer;    -- vertical back porch width in rows
		v_pixels:        integer;    -- vertical display width in rows
		v_front_porch:   integer;    -- vertical front porch width in rows
		v_polarity:      std_ulogic; -- vertical sync pulse polarity (1 = positive, 0 = negative)
	end record;
 
	constant vga_640x480: vga_configuration := (
		clock_frequency => 25_175_000,
		h_pulse =>  96, h_back_porch =>  48, h_pixels =>  640, h_front_porch =>  16, h_polarity => '0',
		v_pulse =>   2, v_back_porch =>  33, v_pixels =>  480, v_front_porch =>  10, v_polarity => '0');
 
	constant vga_800x600: vga_configuration := (
		clock_frequency => 40_000_000,
		h_pulse => 128, h_back_porch =>  88, h_pixels =>  800, h_front_porch =>  40, h_polarity => '1',
		v_pulse =>   4, v_back_porch =>  23, v_pixels =>  600, v_front_porch =>   1, v_polarity => '1');
 
	constant vga_1024x768: vga_configuration := (
		clock_frequency => 44_900_000,
		h_pulse => 176, h_back_porch =>  56, h_pixels => 1024, h_front_porch =>   8, h_polarity => '1',
		v_pulse => 8,   v_back_porch =>  41, v_pixels =>  800, v_front_porch =>   0, v_polarity => '1');
 
	constant vga_1920x1200: vga_configuration := (
		clock_frequency => 193_160_000,
		h_pulse => 208, h_back_porch => 336, h_pixels => 1920, h_front_porch => 128, h_polarity => '0',
		v_pulse => 3,   v_back_porch => 38,  v_pixels => 1200, v_front_porch =>   1, v_polarity => '1');
 
	component vga_controller is
	generic (
		g: common_generics;
		pixel_clock_frequency:  positive := 25_000_000;
		constant cfg: vga_configuration  := vga_640x480);
	port (
		clk, rst:          in std_ulogic;  -- pixel clock, must run at configured frequency
		h_sync, v_sync:   out std_ulogic;  -- sync pulses
		h_blank, v_blank: out std_ulogic;
		column, row:      out integer);   -- pixel coordinates
	end component;
 
	component vga_tb is
		generic (g: common_generics; pixel_clock_frequency: positive := 25_000_000; simulation_us: time := 20000 us);
	end component;
 
	constant led_7_segment_character_length: positive := 4;
	subtype led_7_segment_character is std_ulogic_vector(led_7_segment_character_length - 1 downto 0);
	subtype led_7_segment is std_ulogic_vector(7 downto 0);
 
	component led_7_segment_display is
		generic (g: common_generics;
			use_bcd_not_hex:         boolean := true;
			refresh_rate_us:         natural := 1500;
			number_of_led_displays: positive := 4);
		port (
			clk:      in   std_ulogic;
			rst:      in   std_ulogic;
 
			leds_we:  in   std_ulogic;
			leds:     in   std_ulogic_vector((number_of_led_displays * led_7_segment_character_length) - 1 downto 0);
 
			-- Physical outputs
			an:       out  std_ulogic_vector(number_of_led_displays - 1 downto 0);  -- anodes, controls on/off
			ka:       out  std_ulogic_vector(7 downto 0)); -- cathodes, data on display
	end component;
 
	component led_7_segment_display_tb is
		generic (g: common_generics);
	end component;
 
	component sine is
		generic (g: common_generics; pipeline: boolean := true);
		port (
			clk, rst, xwe: in std_ulogic;
			x:  in  std_ulogic_vector(15 downto 0);
			s:  out std_ulogic_vector(15 downto 0));
	end component;
 
	component cosine is
		generic (g: common_generics; pipeline: boolean := true);
		port (
			clk, rst, xwe: in std_ulogic;
			x:  in  std_ulogic_vector(15 downto 0);
			s:  out std_ulogic_vector(15 downto 0));
	end component;
 
	component sine_tb is
		generic (g: common_generics);
	end component;
 
	function max(a: natural; b: natural) return natural;
	function min(a: natural; b: natural) return natural;
	function reverse (a: in std_ulogic_vector) return std_ulogic_vector;
	function invert(slv:std_ulogic_vector) return std_ulogic_vector;
	function parity(slv:std_ulogic_vector; even: boolean) return std_ulogic;
	function parity(slv:std_ulogic_vector; even: std_ulogic) return std_ulogic;
	function or_reduce(slv:std_ulogic_vector) return std_ulogic;
	function and_reduce(slv:std_ulogic_vector) return std_ulogic;
	function select_bit(indexed, selector: std_ulogic_vector) return std_ulogic;
	function priority(order: std_ulogic_vector; high: boolean) return natural;
	function priority(order: std_ulogic_vector; high: boolean) return std_ulogic_vector;
	function mux(a: std_ulogic_vector; b: std_ulogic_vector; sel: std_ulogic) return std_ulogic_vector;
	function mux(a: std_ulogic; b: std_ulogic; sel: std_ulogic) return std_ulogic;
	function mux(a, b : std_ulogic_vector) return std_ulogic;
	function decode(encoded: std_ulogic_vector) return std_ulogic_vector;
	function to_std_ulogic_vector(s: string) return std_ulogic_vector;
	function logical(b: boolean) return std_ulogic;
	function bit_count_f(s: std_ulogic_vector) return integer;
	function hex_char_to_std_ulogic_vector_tb(hc: character) return std_ulogic_vector;
 
	type ulogic_string is array(integer range <>) of std_ulogic_vector(7 downto 0);
  	function to_std_ulogic_vector(s: string) return ulogic_string;
 
	-- synthesis translate_off
	subtype configuration_name is string(1 to 8);
 
	type configuration_item is record
		name:  configuration_name;
		value: integer;
	end record;
 
	type configuration_items is array(integer range <>) of configuration_item;
 
	function search_configuration_tb(find_me: configuration_name; ci: configuration_items) return integer;
	procedure read_configuration_tb(file_name:  string; ci: inout configuration_items);
	procedure write_configuration_tb(file_name: string; ci: configuration_items);
	-- synthesis translate_on
end;
 
package body util is
 
	function max(a: natural; b: natural) return natural is
	begin
		if (a > b) then return a; else return b; end if;
	end function;
 
	function min(a: natural; b: natural) return natural is
	begin
		if (a < b) then return a; else return b; end if;
	end function;
 
	function n_bits(x: natural) return natural is -- Not synthesizable
		variable x1: natural := max(x, 1) - 1;
		variable n:  natural := 1;
	begin
		while x1 > 1 loop
			x1 := x1 / 2;
			n  := n + 1;
		end loop;
		return n;
	end function;
 
	function n_bits(x: std_ulogic_vector) return natural is -- Not synthesizable
	begin
		return n_bits(x'high);
	end function;
 
	-- <https://stackoverflow.com/questions/13584307>
	function reverse (a: in std_ulogic_vector) return std_ulogic_vector is
		variable result: std_ulogic_vector(a'range);
		alias aa: std_ulogic_vector(a'reverse_range) is a;
	begin
		for i in aa'range loop
			result(i) := aa(i);
		end loop;
		return result;
	end;
 
	function invert(slv: std_ulogic_vector) return std_ulogic_vector is
		variable z: std_ulogic_vector(slv'range);
	begin
		for i in slv'range loop
			z(i) := not(slv(i));
		end loop;
		return z;
	end;
 
	function parity(slv: std_ulogic_vector; even: boolean) return std_ulogic is
		variable z: std_ulogic := '0';
	begin
		if not even then
			z := '1';
		end if;
		for i in slv'range loop
			z := z xor slv(i);
		end loop;
		return z;
	end;
 
	function parity(slv:std_ulogic_vector; even: std_ulogic) return std_ulogic is
		variable z: boolean := false;
	begin
		if even = '1' then
			z := true;
		end if;
		return parity(slv, z);
	end;
 
	function or_reduce(slv:std_ulogic_vector) return std_ulogic is
		variable z: std_ulogic := '0';
	begin
		for i in slv'range loop
			z := z or slv(i);
		end loop;
		return z;
	end;
 
	function and_reduce(slv:std_ulogic_vector) return std_ulogic is
		variable z: std_ulogic := '1';
	begin
		for i in slv'range loop
			z := z and slv(i);
		end loop;
		return z;
	end;
 
	function select_bit(indexed, selector: std_ulogic_vector) return std_ulogic is
		variable z: std_ulogic := 'X';
	begin
		assert n_bits(indexed) = selector'high + 1 severity failure;
		for i in indexed'range loop
			if i = to_integer(unsigned(selector)) then
				z := indexed(i);
			end if;
		end loop;
		return z;
	end;
 
	function priority(order: std_ulogic_vector; high: boolean) return natural is
		variable p: natural := 0;
	begin
		if not high then
			for i in order'high + 1 downto 1 loop
				if order(i-1) = '1' then
					p := i - 1;
				end if;
			end loop;
		else
			for i in 1 to order'high + 1 loop
				if order(i-1) = '1' then
					p := i - 1;
				end if;
			end loop;
		end if;
		return p;
	end;
 
	function priority(order: std_ulogic_vector; high: boolean) return std_ulogic_vector is
		variable length: natural := n_bits(order'length);
	begin
		return std_ulogic_vector(to_unsigned(priority(order, high), length));
	end;
 
	function mux(a: std_ulogic_vector; b: std_ulogic_vector; sel: std_ulogic) return std_ulogic_vector is
		variable m: std_ulogic_vector(a'range) := (others => 'X');
	begin
		if sel = '0' then m := a; else m := b; end if;
		return m;
	end;
 
	function mux(a: std_ulogic; b: std_ulogic; sel: std_ulogic) return std_ulogic is
		variable m: std_ulogic := 'X';
	begin
		if sel = '0' then m := a; else m := b; end if;
		return m;
	end;
 
	function mux(a, b : std_ulogic_vector) return std_ulogic is
		variable r: std_ulogic_vector(b'length - 1 downto 0) := (others => 'X');
		variable i: integer;
	begin
		r := b;
		i := to_integer(unsigned(a));
		return r(i);
	end;
 
	function decode(encoded : std_ulogic_vector) return std_ulogic_vector is
		variable r: std_ulogic_vector((2 ** encoded'length) - 1 downto 0) := (others => '0');
		variable i: natural;
	begin
		i    := to_integer(unsigned(encoded));
		r(i) := '1';
		return r;
	end;
 
	function logical(b: boolean) return std_ulogic is
	begin
		if b then return '1'; else return '0'; end if;
	end;
 
	function hex_char_to_std_ulogic_vector_tb(hc: character) return std_ulogic_vector is
		variable slv: std_ulogic_vector(3 downto 0);
	begin
		case hc is
		when '0' => slv := "0000";
		when '1' => slv := "0001";
		when '2' => slv := "0010";
		when '3' => slv := "0011";
		when '4' => slv := "0100";
		when '5' => slv := "0101";
		when '6' => slv := "0110";
		when '7' => slv := "0111";
		when '8' => slv := "1000";
		when '9' => slv := "1001";
		when 'A' => slv := "1010";
		when 'a' => slv := "1010";
		when 'B' => slv := "1011";
		when 'b' => slv := "1011";
		when 'C' => slv := "1100";
		when 'c' => slv := "1100";
		when 'D' => slv := "1101";
		when 'd' => slv := "1101";
		when 'E' => slv := "1110";
		when 'e' => slv := "1110";
		when 'F' => slv := "1111";
		when 'f' => slv := "1111";
		when others => slv := "XXXX";
		end case;
		assert (slv /= "XXXX") report " not a valid hex character: " & hc  severity failure;
		return slv;
	end;
 
	function bit_count_f(s : std_ulogic_vector) return integer is
		variable count: natural := 0;
	begin
		for i in s'range loop
			if s(i) = '1' then
				count := count + 1;
			end if;
		end loop;
		return count;
	end;
 
	-- <https://stackoverflow.com/questions/30519849/vhdl-convert-string-to-std-logic-vector>
	function to_std_ulogic_vector(s: string) return std_ulogic_vector is
	    variable ret: std_ulogic_vector(s'length*8-1 downto 0);
	begin
	    for i in s'range loop
		ret(i*8+7 downto i*8) := std_ulogic_vector(to_unsigned(character'pos(s(i)), 8));
	    end loop;
	    return ret;
	end;
 
	function to_std_ulogic_vector(s: string) return ulogic_string is
	    variable ret: ulogic_string(s'range);
	begin
		for i in s'range loop
			ret(i) := std_ulogic_vector(to_unsigned(character'pos(s(i)), 8));
		end loop;
		return ret;
	end;
 
	-- synthesis translate_off
 
	-- Find a string in a configuration items array, or returns -1 on
	-- failure to find the string.
	function search_configuration_tb(find_me: configuration_name; ci: configuration_items) return integer is
	begin
		for i in ci'range loop
			if ci(i).name = find_me then
				return i;
			end if;
		end loop;
		return -1;
	end;
 
	-- VHDL provides quite a limited set of options for dealing with
	-- operations that are not synthesizeable but would be useful for
	-- in test benches. This method provides a crude way of reading
	-- in configurable options. It has a very strict format.
	--
	-- The format is line oriented, it expects a string on a line
	-- with a length equal to the "configuration_name" type, which
	-- is a subtype of "string". It finds the corresponding record
	-- in configuration_items if it exists. It then reads in an
	-- integer from the next line and sets the record for it.
	--
	-- Any deviation from this format causes an error and the simulation
	-- to halt, whilst not a good practice to do error checking with asserts
	-- there is no better way in VHDL in this case. The only sensible
	-- action on an error would for the configuration file to be fixed
	-- anyway.
	--
	-- Comment lines and variable length strings would be nice, but
	-- are too much of a hassle.
	--
	-- The configuration function only deal with part of the configuration
	-- process, it does not deal with deserialization into structures
	-- more useful to the user - like into individual signals.
	--
	procedure read_configuration_tb(file_name: string; ci: inout configuration_items) is
		file     in_file: text is in file_name;
		variable in_line: line;
		variable d:       integer;
		variable s:       configuration_name;
		variable index:   integer;
	begin
		while not endfile(in_file) loop
 
			readline(in_file, in_line);
			read(in_line, s);
			index := search_configuration_tb(s, ci);
 
			assert index >= 0 report "Unknown configuration item: " & s severity failure;
 
			readline(in_file, in_line);
			read(in_line, d);
 
			ci(index).value := d;
 
			report "Config Item: '" & ci(index).name & "' = " & integer'image(ci(index).value);
		end loop;
		file_close(in_file);
	end procedure;
 
	procedure write_configuration_tb(file_name: string; ci: configuration_items) is
		file     out_file: text is out file_name;
		variable out_line: line;
	begin
		for i in ci'range loop
			write(out_line, ci(i).name);
			writeline(out_file, out_line);
			write(out_line, ci(i).value);
			writeline(out_file, out_line);
		end loop;
	end procedure;
 
	-- synthesis translate_on
end;
 
------------------------- Utility Test Bench ----------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity util_tb is
	generic (g: common_generics);
end entity;
 
architecture behav of util_tb is
begin
	-- The "io_pins_tb" works correctly, however in GHDL 0.29, compiled under
	-- Windows, fails to simulate this component correctly, resulting
	-- in a crash. This does not affect the Linux build of GHDL. It has
	-- something to do with 'Z' values for std_logic types.
	uut_io_pins:  work.util.io_pins_tb              generic map (g => g);
	uut_timer_us: work.util.timer_us_tb             generic map (g => g);
	uut_full_add: work.util.full_adder_tb           generic map (g => g);
	uut_fifo:     work.util.fifo_tb                 generic map (g => g);
	uut_counter:  work.util.counter_tb              generic map (g => g);
	uut_ucpu:     work.util.ucpu_tb                 generic map (g => g);
	uut_rdivider: work.util.restoring_divider_tb    generic map (g => g);
	uut_debounce: work.util.debounce_us_tb          generic map (g => g);
	uut_rst_gen:  work.util.reset_generator_tb      generic map (g => g);
	uut_bit_cnt:  work.util.bit_count_tb            generic map (g => g);
	uut_majority: work.util.majority_tb             generic map (g => g);
	uut_delay_ln: work.util.delay_line_tb           generic map (g => g);
	uut_rising:   work.util.rising_edge_detector_tb generic map (g => g);
	uut_shiftReg: work.util.shift_register_tb       generic map (g => g);
	uut_lfsr:     work.util.lfsr_tb                 generic map (g => g);
	uut_gray:     work.util.gray_tb                 generic map (g => g);
	uut_ham:      work.util.hamming_7_4_tb          generic map (g => g); -- Oink!
	uut_vga:      work.util.vga_tb                  generic map (g => g, simulation_us => 1 us);
	uut_sine:     work.util.sine_tb                 generic map (g => g);
	uut_7_seg:   work.util.led_7_segment_display_tb generic map (g => g);
 
	stimulus_process: process
	begin
		assert max(5, 4)                 =  5      severity failure;
		assert work.util.min(5, 4)       =  4      severity failure;
		assert n_bits(1)                 =  1      severity failure;
		assert n_bits(2)                 =  1      severity failure;
		assert n_bits(7)                 =  3      severity failure;
		assert n_bits(8)                 =  3      severity failure;
		assert n_bits(9)                 =  4      severity failure;
		assert reverse("1")              =  "1"    severity failure;
		assert reverse("0")              =  "0"    severity failure;
		assert reverse("10")             =  "01"   severity failure;
		assert reverse("11")             =  "11"   severity failure;
		assert reverse("0101")           =  "1010" severity failure;
		assert invert("1")               =  "0"    severity failure;
		assert invert("0")               =  "1"    severity failure;
		assert invert("0101")            =  "1010" severity failure;
		assert select_bit("01000", "01") =  '1'    severity failure;
		assert parity("0", true)         =  '0'    severity failure;
		assert parity("1", true)         =  '1'    severity failure;
		assert parity("11", true)        =  '0'    severity failure;
		assert parity("1010001", true)   =  '1'    severity failure;
		assert parity("0", false)        =  '1'    severity failure;
		assert parity("1", false)        =  '0'    severity failure;
		assert parity("11", false)       =  '1'    severity failure;
		assert parity("1010001", false)  =  '0'    severity failure;
		assert or_reduce("0000")         =  '0'    severity failure;
		assert or_reduce("0")            =  '0'    severity failure;
		assert or_reduce("1")            =  '1'    severity failure;
		assert or_reduce("11")           =  '1'    severity failure;
		assert or_reduce("10")           =  '1'    severity failure;
		assert or_reduce("01")           =  '1'    severity failure;
		assert and_reduce("01")          =  '0'    severity failure;
		assert and_reduce("11")          =  '1'    severity failure;
		assert and_reduce("1")           =  '1'    severity failure;
		assert and_reduce("0")           =  '0'    severity failure;
		assert and_reduce("10")          =  '0'    severity failure;
		assert priority("01001", false)  =  1      severity failure;
		assert mux("1010", "0101", '0')  =  "1010" severity failure;
		assert mux("1010", "0101", '1')  =  "0101" severity failure;
		assert decode("00")              =  "0001" severity failure;
		assert decode("01")              =  "0010" severity failure;
		assert decode("10")              =  "0100" severity failure;
		assert decode("11")              =  "1000" severity failure;
		wait;
	end process;
end architecture;
 
------------------------- Function Test Bench ---------------------------------------
 
------------------------- Test bench clock source -----------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.util.common_generics;
 
entity clock_source_tb is
	generic (g: common_generics; hold_rst: positive);
	port (
		stop:            in     std_ulogic := '0';
		clk:             out    std_ulogic;
		clk_with_jitter: out    std_ulogic := '0';
		rst:             out    std_ulogic := '0');
end entity;
 
architecture rtl of clock_source_tb is
	constant clock_period: time      :=  1000 ms / g.clock_frequency;
	signal jitter_delay:   time      := 0 ns;
	signal jitter_clk:     std_ulogic := '0';
begin
	jitter_clk_process: process
		variable seed1, seed2: positive;
		variable r: real;
		variable jit_high, jit_low: time  := 0 ns;
	begin
		while stop = '0' loop
			uniform(seed1, seed2, r);
			jit_high := r * g.delay;
			uniform(seed1, seed2, r);
			jit_low := r * g.delay;
			uniform(seed1, seed2, r);
			if r < 0.5 then jit_high := -jit_high; end if;
			uniform(seed1, seed2, r);
			if r < 0.5 then jit_low := -jit_low; end if;
			clk_with_jitter <= '1';
			wait for (clock_period / 2) + jit_high;
			clk_with_jitter <= '0';
			wait for (clock_period / 2) + jit_low;
		end loop;
		wait;
	end process;
 
	clk_process: process
	begin
		while stop = '0' loop
			clk <= '1';
			wait for clock_period / 2;
			clk <= '0';
			wait for clock_period / 2;
		end loop;
		wait;
	end process;
 
	rst_process: process
	begin
		rst <= '1';
		wait for clock_period * hold_rst;
		rst <= '0';
		wait;
	end process;
 
end architecture;
 
------------------------- Generic Register of std_ulogic_vector ----------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity reg is
	generic (g: common_generics; N: positive);
	port (
		clk: in  std_ulogic;
		rst: in  std_ulogic;
		we:  in  std_ulogic;
		di:  in  std_ulogic_vector(N - 1 downto 0);
		do:  out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture rtl of reg is
	signal r_c, r_n: std_ulogic_vector(N - 1 downto 0) := (others => '0');
begin
	do <= r_c after g.delay;
 
	process(rst, clk)
	begin
		if rst = '1' and g.asynchronous_reset then
			r_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				r_c <= (others => '0') after g.delay;
			else
				r_c <= r_n after g.delay;
			end if;
		end if;
	end process;
 
	process(r_c, di, we)
	begin
		if we = '1' then
			r_n <= di after g.delay;
		else
			r_n <= r_c after g.delay;
		end if;
	end process;
end;
 
------------------------- Generic Register of std_ulogic_vector ----------------------
 
------------------------- Shift register --------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
-- https://stackoverflow.com/questions/36342960/optional-ports-in-vhdl
entity shift_register is
	generic (g: common_generics; N: positive);
	port (
		clk:     in  std_ulogic;
		rst:     in  std_ulogic;
		we:      in  std_ulogic;
		di:      in  std_ulogic;
		do:      out std_ulogic;
 
		-- optional
		load_we: in  std_ulogic := '0';
		load_i:  in  std_ulogic_vector(N - 1 downto 0) := (others => '0');
		load_o:  out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture rtl of shift_register is
	signal r_c, r_n : std_ulogic_vector(N - 1 downto 0) := (others => '0');
begin
	do     <= r_c(0);
	load_o <= r_c;
 
	process(rst, clk)
	begin
		if rst = '1' and g.asynchronous_reset then
			r_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				r_c <= (others => '0') after g.delay;
			else
				r_c <= r_n after g.delay;
			end if;
		end if;
	end process;
 
	process(r_c, di, we, load_i, load_we)
	begin
		if load_we = '1' then
			r_n <= load_i after g.delay;
		else
			r_n <= "0" & r_c(N - 1 downto 1) after g.delay;
			if we = '1' then
				r_n(N-1) <= di after g.delay;
			end if;
		end if;
	end process;
end;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity shift_register_tb is
	generic (g: common_generics);
end entity;
 
architecture behav of shift_register_tb is
	constant N: positive := 8;
	constant clock_period: time :=  1000 ms / g.clock_frequency;
	signal we: std_ulogic := '0';
	signal di: std_ulogic := '0';
	signal do: std_ulogic := '0';
 
	signal clk, rst: std_ulogic := '0';
	signal stop: std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.shift_register
	generic map (g => g, N => N) port map (clk => clk, rst => rst, we => we, di => di, do => do);
 
	stimulus_process: process
	begin
		-- Put a bit into the shift register and wait
		-- for it to come out the other size.
		wait until rst = '0';
		di <= '1';
		we <= '1';
		wait for clock_period;
		di <= '0';
		we <= '0';
		for I in 0 to 7 loop
			assert do = '0' report "bit appeared to quickly";
			wait for clock_period;
		end loop;
		assert do = '1' report "bit disappeared in shift register";
		wait for clock_period * 1;
		assert do = '0' report "extra bit set in shift register";
		stop <= '1';
		wait;
	end process;
end;
------------------------- Shift register --------------------------------------------
 
------------------------- Microsecond Timer -----------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.max;
use work.util.n_bits;
use work.util.common_generics;
 
entity timer_us is
	generic (g: common_generics; timer_period_us: natural);
	port (
		clk: in  std_ulogic;
		rst: in  std_ulogic;
		co:  out std_ulogic);
end timer_us;
 
architecture rtl of timer_us is
	constant cycles:   natural := (g.clock_frequency / 1000000) * timer_period_us;
	subtype  counter is unsigned(max(1, n_bits(cycles) - 1) downto 0);
	signal   c_c, c_n: counter := (others => '0');
begin
	process (clk, rst)
	begin
		if rst = '1' and g.asynchronous_reset then
			c_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				c_c <= (others => '0') after g.delay;
			else
				c_c <= c_n after g.delay;
			end if;
		end if;
	end process;
 
	process (c_c)
	begin
		if c_c = (cycles - 1) then
			c_n <= (others => '0') after g.delay;
			co  <= '1' after g.delay;
		else
			c_n <= c_c + 1 after g.delay;
			co  <= '0' after g.delay;
		end if;
	end process;
end;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity timer_us_tb is
	generic (g: common_generics);
end;
 
architecture behav of timer_us_tb is
	constant clock_period: time := 1000 ms / g.clock_frequency;
	signal co: std_ulogic        := 'X';
	signal clk, rst: std_ulogic  := '0';
	signal stop: std_ulogic      := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.timer_us
		generic map (g => g, timer_period_us => 1)
		port map (clk => clk, rst => rst, co => co);
 
	stimulus_process: process
	begin
		wait for 1 us;
		assert co = '0' severity failure;
		wait for clock_period;
		assert co = '1' severity failure;
		stop <= '1';
		wait;
	end process;
end;
 
------------------------- Microsecond Timer -----------------------------------------
 
------------------------- Rising Edge Detector --------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity rising_edge_detector is
	generic (g: common_generics);
	port (
		clk:    in  std_ulogic;
		rst:    in  std_ulogic;
		di:     in  std_ulogic;
		do:     out std_ulogic);
end;
 
architecture rtl of rising_edge_detector is
	signal sin_0: std_ulogic := '0';
	signal sin_1: std_ulogic := '0';
begin
	red: process(clk, rst)
	begin
		if rst = '1' and g.asynchronous_reset then
			sin_0 <= '0' after g.delay;
			sin_1 <= '0' after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				sin_0 <= '0' after g.delay;
				sin_1 <= '0' after g.delay;
			else
				sin_0 <=    di after g.delay;
				sin_1 <= sin_0 after g.delay;
			end if;
		end if;
	end process;
	do <= not sin_1 and sin_0;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity rising_edge_detector_tb is
	generic (g: common_generics);
end;
 
architecture behav of rising_edge_detector_tb is
	constant clock_period: time := 1000 ms / g.clock_frequency;
	signal di:  std_ulogic := '0';
	signal do: std_ulogic := 'X';
 
	signal clk, rst: std_ulogic := '0';
	signal stop: std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.rising_edge_detector
		generic map (g => g)
		port map (clk => clk, rst => rst, di => di, do => do);
 
	stimulus_process: process
	begin
		wait for clock_period * 5;
		assert do = '0' severity failure;
		wait for clock_period;
		di <= '1';
		wait for clock_period * 0.5;
		assert do = '1' severity failure;
		wait for clock_period * 1.5;
		di <= '0';
		assert do = '0' severity failure;
		wait for clock_period;
		assert do = '0' severity failure;
 
		assert stop = '0' report "Test bench not run to completion";
		stop <= '1';
		wait;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity rising_edge_detectors is
	generic (g: common_generics; N: positive);
	port (
		clk:    in  std_ulogic;
		rst:    in  std_ulogic;
		di:     in  std_ulogic_vector(N - 1 downto 0);
		do:     out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture structural of rising_edge_detectors is
begin
	changes: for i in N - 1 downto 0 generate
		d_instance: work.util.rising_edge_detector
			generic map (g => g)
			port map (clk => clk, rst => rst, di => di(i), do => do(i));
	end generate;
end architecture;
 
------------------------- Rising Edge Detector --------------------------------------
 
------------------------- Half Adder ------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity half_adder is
	generic (g: common_generics);
	port (
		a:     in  std_ulogic;
		b:     in  std_ulogic;
		sum:   out std_ulogic;
		carry: out std_ulogic);
end entity;
 
architecture rtl of half_adder is
begin
	sum   <= a xor b after g.delay;
	carry <= a and b after g.delay;
end architecture;
 
------------------------- Half Adder ------------------------------------------------
 
------------------------- Full Adder ------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity full_adder is
	generic (g: common_generics);
	port (
		x:     in    std_ulogic;
		y:     in    std_ulogic;
		z:     in    std_ulogic;
		sum:   out   std_ulogic;
		carry: out   std_ulogic);
end entity;
 
architecture rtl of full_adder is
	signal carry1, carry2, sum1: std_ulogic;
begin
	ha1: entity work.half_adder generic map (g => g) port map (a => x,    b => y, sum => sum1, carry => carry1);
	ha2: entity work.half_adder generic map (g => g) port map (a => sum1, b => z, sum => sum,  carry => carry2);
	carry <= carry1 or carry2 after g.delay;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity full_adder_tb is
	generic (g: common_generics);
end entity;
 
architecture behav of full_adder_tb is
	constant clock_period: time  := 1000 ms / g.clock_frequency;
	signal x, y, z:    std_ulogic := '0';
	signal sum, carry: std_ulogic := '0';
 
	type stimulus_data   is array (0 to 7)              of std_ulogic_vector(2 downto 0);
	type stimulus_result is array (stimulus_data'range) of std_ulogic_vector(0 to     1);
 
	constant data: stimulus_data := (
		0 => "000", 1 => "001",
		2 => "010", 3 => "011",
		4 => "100", 5 => "101",
		6 => "110", 7 => "111");
 
	constant result: stimulus_result := (
		0 => "00",  1 => "10",
		2 => "10",  3 => "01",
		4 => "10",  5 => "01",
		6 => "01",  7 => "11");
 
	signal clk, rst: std_ulogic := '0';
	signal stop: std_ulogic     := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.full_adder
		generic map (g => g)
		port map (x => x, y => y, z => z, sum => sum, carry => carry);
 
	stimulus_process: process
	begin
		wait for clock_period;
		for i in data'range loop
			x <= data(i)(0);
			y <= data(i)(1);
			z <= data(i)(2);
			wait for clock_period;
			assert sum = result(i)(0) and carry = result(i)(1)
				report
					"For: "       & std_ulogic'image(x) & std_ulogic'image(y) & std_ulogic'image(z) &
					" Got: "      & std_ulogic'image(sum)          & std_ulogic'image(carry) &
					" Expected: " & std_ulogic'image(result(i)(0)) & std_ulogic'image(result(i)(1))
				severity failure;
			wait for clock_period;
		end loop;
 
		stop <= '1';
		wait;
	end process;
end architecture;
 
------------------------- Full Adder ------------------------------------------------
 
------------------------- FIFO ------------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity fifo is
	generic (g: common_generics;
		data_width: positive;
		fifo_depth: positive;
		read_first: boolean := true);
	port (
		clk:   in  std_ulogic;
		rst:   in  std_ulogic;
		di:    in  std_ulogic_vector(data_width - 1 downto 0);
		we:    in  std_ulogic;
		re:    in  std_ulogic;
		do:    out std_ulogic_vector(data_width - 1 downto 0);
 
		-- optional
		full:  out std_ulogic := '0';
		empty: out std_ulogic := '1');
end fifo;
 
architecture behavior of fifo is
	type fifo_data_t is array (0 to fifo_depth - 1) of std_ulogic_vector(di'range);
	signal data: fifo_data_t := (others => (others => '0'));
	function rindex_init return integer is
	begin
		if read_first then
			return 0;
		end if;
		return fifo_depth - 1;
	end function;
 
	signal count:  integer range 0 to fifo_depth := 0;
	signal windex: integer range 0 to fifo_depth - 1 := 0;
	signal rindex: integer range 0 to fifo_depth - 1 := rindex_init;
 
	signal is_full:  std_ulogic := '0';
	signal is_empty: std_ulogic := '1';
begin
	do       <= data(rindex) after g.delay;
	full     <= is_full after g.delay;  -- buffer these bad boys
	empty    <= is_empty after g.delay;
	is_full  <= '1' when count = fifo_depth else '0' after g.delay;
	is_empty <= '1' when count = 0          else '0' after g.delay;
 
	process (rst, clk) is
	begin
		if rst = '1' and g.asynchronous_reset then
			windex <= 0 after g.delay;
			count  <= 0 after g.delay;
			rindex <= rindex_init after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				windex <= 0 after g.delay;
				count  <= 0 after g.delay;
				rindex <= rindex_init after g.delay;
			else
				if we = '1' and re = '0' then
					if is_full = '0' then
						count <= count + 1 after g.delay;
					end if;
				elsif we = '0' and re = '1' then
					if is_empty = '0' then
						count <= count - 1 after g.delay;
					end if;
				end if;
 
				if re = '1' and is_empty = '0' then
					if rindex = (fifo_depth - 1) then
						rindex <= 0 after g.delay;
					else
						rindex <= rindex + 1 after g.delay;
					end if;
				end if;
 
				if we = '1' and is_full = '0' then
					if windex = (fifo_depth - 1) then
						windex <= 0 after g.delay;
					else
						windex <= windex + 1 after g.delay;
					end if;
					data(windex) <= di after g.delay;
				end if;
			end if;
		end if;
	end process;
end behavior;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity fifo_tb is
	generic (g: common_generics);
end entity;
 
architecture behavior of fifo_tb is
	constant clock_period: time  := 1000 ms / g.clock_frequency;
	constant data_width: positive := 8;
	constant fifo_depth: positive := 16;
 
	signal di:       std_ulogic_vector(data_width - 1 downto 0) := (others => '0');
	signal re:       std_ulogic := '0';
	signal we:       std_ulogic := '0';
 
	signal do:       std_ulogic_vector(data_width - 1 downto 0) := (others => '0');
	signal empty:    std_ulogic := '0';
	signal full:     std_ulogic := '0';
 
	signal clk, rst: std_ulogic := '0';
	signal stop_w:   std_ulogic := '0';
	signal stop_r:   std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	stop <= '1' when stop_w = '1' and stop_r = '1' else '0';
 
	uut: entity work.fifo
		generic map (g => g, data_width => data_width, fifo_depth => fifo_depth)
		port map (
			clk   => clk,
			rst   => rst,
			di    => di,
			we    => we,
			re    => re,
			do    => do,
			full  => full,
			empty => empty);
 
	write_process: process
		variable counter: unsigned (data_width - 1 downto 0) := (others => '0');
	begin
		wait for clock_period * 20;
 
		for i in 1 to 32 loop
			counter := counter + 1;
			di <= std_ulogic_vector(counter);
			wait for clock_period * 1;
			we <= '1';
			wait for clock_period * 1;
			we <= '0';
		end loop;
 
		wait for clock_period * 20;
 
		for i in 1 to 32 loop
			counter := counter + 1;
			di <= std_ulogic_vector(counter);
			wait for clock_period * 1;
			we <= '1';
			wait for clock_period * 1;
			we <= '0';
		end loop;
 
		stop_w <= '1';
		wait;
	end process;
 
	read_process: process
	begin
		wait for clock_period * 60;
		re <= '1';
		wait for clock_period * 60;
		re <= '0';
		wait for clock_period * 256 * 2;
		re <= '1';
 
		stop_r <= '1';
		wait;
	end process;
end architecture;
 
------------------------- FIFO ------------------------------------------------------
 
------------------------- Free running counter --------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity counter is
	generic (g: common_generics;
		N:                    positive);
	port (
		clk:     in  std_ulogic;
		rst:     in  std_ulogic;
		ce:      in  std_ulogic;
		cr:      in  std_ulogic;
		dout:    out std_ulogic_vector(N - 1 downto 0);
 
		-- optional
		load_we: in  std_ulogic := '0';
		load_i:  in  std_ulogic_vector(N - 1 downto 0) := (others => '0'));
end entity;
 
architecture rtl of counter is
	signal c_c, c_n: unsigned(N - 1 downto 0) := (others => '0');
begin
	dout <= std_ulogic_vector(c_c) after g.delay;
 
	process(clk, rst)
	begin
		if rst = '1' and g.asynchronous_reset then
			c_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				c_c <= (others => '0') after g.delay;
			else
				c_c <= c_n after g.delay;
			end if;
		end if;
	end process;
 
	process(c_c, cr, ce, load_we, load_i)
	begin
		c_n <= c_c;
		if load_we = '1' then
			c_n <= unsigned(load_i) after g.delay;
		else
			if cr = '1' then
				c_n <= (others => '0') after g.delay;
			elsif ce = '1' then
				c_n <= c_c + 1 after g.delay;
			end if;
		end if;
	end process;
 
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity counter_tb is
	generic (g: common_generics);
end entity;
 
architecture behavior of counter_tb is
	constant clock_period: time     := 1000 ms / g.clock_frequency;
	constant length:       positive := 2;
 
	-- inputs
	signal ce: std_ulogic := '0';
	signal cr: std_ulogic := '0';
 
	-- outputs
	signal dout: std_ulogic_vector(length - 1 downto 0);
 
	-- test data
	type stimulus_data   is array (0 to 16)             of std_ulogic_vector(1 downto 0);
	type stimulus_result is array (stimulus_data'range) of std_ulogic_vector(0 to     1);
 
	constant data: stimulus_data := (
		 0 => "00",  1 => "00",
		 2 => "01",  3 => "01",
		 4 => "00",  5 => "00",
		 6 => "10",  7 => "00",
		 8 => "01",  9 => "01",
		10 => "11", 11 => "00",
		12 => "01", 13 => "01",
		14 => "01", 15 => "01",
		16 => "01");
 
	constant result: stimulus_result := (
		 0 => "00",  1 => "00",
		 2 => "00",  3 => "01",
		 4 => "10",  5 => "10",
		 6 => "10",  7 => "00",
		 8 => "00",  9 => "01",
		10 => "10", 11 => "00",
		12 => "00", 13 => "01",
		14 => "10", 15 => "11",
		16 => "00");
 
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.counter
		generic map (g => g, N => length)
		port map (
			clk   => clk,
			rst   => rst,
			ce    => ce,
			cr    => cr,
			dout  => dout);
 
	stimulus_process: process
	begin
		wait for clock_period;
		for i in data'range loop
			ce <= data(i)(0);
			cr <= data(i)(1);
			wait for clock_period;
			assert dout = result(i)
				report
					"For: ce("    & std_ulogic'image(ce) & ") cr(" & std_ulogic'image(cr) & ") " &
					" Got: "      & integer'image(to_integer(unsigned(dout))) &
					" Expected: " & integer'image(to_integer(unsigned(result(i))))
				severity failure;
		end loop;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- Free running counter --------------------------------------
 
------------------------- Linear Feedback Shift Register ----------------------------
-- For good sources on LFSR see
-- * https://sites.ualberta.ca/~delliott/ee552/studentAppNotes/1999f/Drivers_Ed/lfsr.html
-- * https://en.wikipedia.org/wiki/Linear-feedback_shift_register
--
-- Some optimal taps
--
-- Taps start at the left most std_ulogic element of tap at '0' and proceed to
-- the highest bit. To instantiate an instance of the LFSR set tap to a
-- standard logic vector one less the size of LFSR that you want. An 8-bit
-- LFSR can be made by setting it 'tap' to "0111001". The LFSR will need to
-- be loaded with a seed value, set 'do' to that value and assert 'we'. The
-- LFSR will only run when 'ce' is asserted, otherwise it will preserve the
-- current value.
--
-- Number of bits   Taps      Cycle Time
--      8           1,2,3,7    255
--      16          1,2,4,15   65535
--      32          1,5,6,31   4294967295
--
-- This component could also be used to calculate CRCs, which are basically
-- the same computation.
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity lfsr is
	generic (g: common_generics; constant tap: std_ulogic_vector);
	port
	(
		clk: in  std_ulogic;
		rst: in  std_ulogic;
		ce:  in  std_ulogic := '1';
		we:  in  std_ulogic;
		di:  in  std_ulogic_vector(tap'high + 1 downto tap'low);
		do:  out std_ulogic_vector(tap'high + 1 downto tap'low));
end entity;
 
architecture rtl of lfsr is
	signal r_c, r_n : std_ulogic_vector(di'range) := (others => '0');
begin
	do <= r_c after g.delay;
 
	process(rst, clk)
	begin
		if rst = '1' and g.asynchronous_reset then
			r_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				r_c <= (others => '0') after g.delay;
			else
				r_c <= r_n after g.delay;
			end if;
		end if;
	end process;
 
	process(r_c, di, we, ce)
	begin
		if we = '1' then
			r_n <= di after g.delay;
		elsif ce = '1' then
			r_n(r_n'high) <= r_c(r_c'low);
			for i in tap'high downto tap'low loop
				if tap(i) = '1' then
					r_n(i) <= r_c(r_c'low) xor r_c(i+1) after g.delay;
				else
					r_n(i) <= r_c(i+1) after g.delay;
				end if;
			end loop;
		else
			r_n <= r_c;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity lfsr_tb is
	generic (g: common_generics);
end entity;
 
architecture behavior of lfsr_tb is
	constant clock_period: time     := 1000 ms / g.clock_frequency;
	signal we: std_ulogic := '0';
	signal do, di: std_ulogic_vector(7 downto 0) := (others => '0');
 
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.lfsr
		generic map (g => g, tap => "0111001")
		port map (clk => clk,
			 rst => rst,
			 we => we,
			 di => di,
			 do => do);
 
	stimulus_process: process
	begin
		wait for clock_period * 2;
		we   <= '1';
		di   <= "00000001";
		wait for clock_period;
		we   <= '0';
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- Linear Feedback Shift Register ----------------------------
 
------------------------- I/O Pin Controller ----------------------------------------
--
-- This is a simple I/O pin control module, there is a control register which
-- sets whether the pins are to be read in (control = '0') or set to the value written to
-- "din" (control = '1').
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity io_pins is
	generic (g: common_generics; N: positive);
	port
	(
		clk:         in    std_ulogic;
		rst:         in    std_ulogic;
		control:     in    std_ulogic_vector(N - 1 downto 0);
		control_we:  in    std_ulogic;
		din:         in    std_ulogic_vector(N - 1 downto 0);
		din_we:      in    std_ulogic;
		dout:        out   std_ulogic_vector(N - 1 downto 0);
		pins:        inout std_logic_vector(N - 1 downto 0));
end entity;
 
architecture rtl of io_pins is
	signal control_o: std_ulogic_vector(control'range) := (others => '0');
	signal din_o:     std_ulogic_vector(din'range)     := (others => '0');
begin
 
	control_r: entity work.reg generic map (g => g, N => N)
				port map (clk => clk, rst => rst, di => control, we => control_we, do => control_o);
	din_r:     entity work.reg generic map (g => g, N => N)
				port map (clk => clk, rst => rst, di => din,     we => din_we,     do => din_o);
 
	pins_i: for i in control_o'range generate
		dout(i) <= pins(i)  when control_o(i) = '0' else '0' after g.delay;
		pins(i) <= din_o(i) when control_o(i) = '1' else 'Z' after g.delay;
	end generate;
 
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity io_pins_tb is
	generic (g: common_generics);
end entity;
 
architecture behavior of io_pins_tb is
	constant clock_period: time := 1000 ms / g.clock_frequency;
	constant N: positive := 8;
 
	signal control: std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal din:     std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal dout:    std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal pins:    std_logic_vector(N - 1 downto 0)  := (others => 'L'); -- !
 
	signal control_we: std_ulogic := '0';
	signal din_we: std_ulogic     := '0';
 
 
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.io_pins
		generic map (g => g, N => N)
		port map (
			clk         =>  clk,
			rst         =>  rst,
			control     =>  control,
			control_we  =>  control_we,
			din         =>  din,
			din_we      =>  din_we,
			dout        =>  dout,
			pins        =>  pins);
 
	stimulus_process: process
	begin
		wait for clock_period * 2;
		control    <= x"0f"; -- write lower pins
		control_we <= '1';
 
		wait for clock_period;
		din        <= x"AA";
		din_we     <= '1';
 
		wait for clock_period * 2;
		pins <= (others => 'H'); -- !
		wait for clock_period * 2;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- I/O Pin Controller ----------------------------------------
 
------------------------- Single and Dual Port Block RAM ----------------------------
--| @warning The function initialize_ram has to be present in each architecture
--| block ram that uses it (as far as I am aware) which means they could fall
--| out of sync. This could be remedied with VHDL-2008.
---------------------------------------------------------------------------------
 
--- Dual Port Model ---
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.util.all;
 
entity dual_port_block_ram is
 
	-- The dual port Block RAM module can be initialized from a file,
	-- or initialized to all zeros. The model can be synthesized (with
	-- Xilinx's ISE) into BRAM.
	--
	-- Valid file_type options include:
	--
	-- FILE_BINARY - A binary file (ASCII '0' and '1', one number per line)
	-- FILE_HEX    - A Hex file (ASCII '0-9' 'a-f', 'A-F', one number per line)
	-- FILE_NONE   - RAM contents will be defaulted to all zeros, no file will
	--               be read from
	--
	-- The data length must be divisible by 4 if the "hex" option is
	-- given.
	--
	-- These default values for addr_length and data_length have been
	-- chosen so as to fill the block RAM available on a Spartan 6.
	--
	generic (g: common_generics;
		addr_length: positive    := 12;
		data_length: positive    := 16;
		file_name:   string      := "memory.bin";
		file_type:   file_format := FILE_BINARY);
	port (
		--| Port A of dual port RAM
		a_clk:  in  std_ulogic;
		a_dwe:  in  std_ulogic;
		a_dre:  in  std_ulogic;
		a_addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		a_din:  in  std_ulogic_vector(data_length - 1 downto 0);
		a_dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0');
		--| Port B of dual port RAM
		b_clk:  in  std_ulogic;
		b_dwe:  in  std_ulogic;
		b_dre:  in  std_ulogic;
		b_addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		b_din:  in  std_ulogic_vector(data_length - 1 downto 0);
		b_dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0'));
end entity;
 
architecture behav of dual_port_block_ram is
	constant ram_size: positive := 2 ** addr_length;
 
	type ram_type is array ((ram_size - 1 ) downto 0) of std_ulogic_vector(data_length - 1 downto 0);
 
	impure function initialize_ram(the_file_name: in string; the_file_type: in file_format) return ram_type is
		variable ram_data:   ram_type;
		file     in_file:    text is in the_file_name;
		variable input_line: line;
		variable tmp:        bit_vector(data_length - 1 downto 0);
		variable c:          character;
		variable slv:        std_ulogic_vector(data_length - 1 downto 0);
	begin
		for i in 0 to ram_size - 1 loop
			if the_file_type = FILE_NONE then
				ram_data(i):=(others => '0');
			elsif not endfile(in_file) then
				readline(in_file,input_line);
				if the_file_type = FILE_BINARY then
					read(input_line, tmp);
					ram_data(i) := std_ulogic_vector(to_stdlogicvector(tmp));
				elsif the_file_type = FILE_HEX then
					assert (data_length mod 4) = 0 report "(data_length%4)!=0" severity failure;
					for j in 1 to (data_length/4) loop
						c:= input_line((data_length/4) - j + 1);
						slv((j*4)-1 downto (j*4)-4) := hex_char_to_std_ulogic_vector_tb(c);
					end loop;
					ram_data(i) := slv;
				else
					report "Incorrect file type given: " & file_format'image(the_file_type) severity failure;
				end if;
			else
				ram_data(i) := (others => '0');
			end if;
		end loop;
		file_close(in_file);
		return ram_data;
	end function;
 
	shared variable ram: ram_type := initialize_ram(file_name, file_type);
 
begin
	a_ram: process(a_clk)
	begin
		if rising_edge(a_clk) then
			if a_dwe = '1' then
				ram(to_integer(unsigned(a_addr))) := a_din;
			end if;
			if a_dre = '1' then
				a_dout <= ram(to_integer(unsigned(a_addr))) after g.delay;
			else
				a_dout <= (others => '0') after g.delay;
			end if;
		end if;
	end process;
 
	b_ram: process(b_clk)
	begin
		if rising_edge(b_clk) then
			if b_dwe = '1' then
				ram(to_integer(unsigned(b_addr))) := b_din;
			end if;
			if b_dre = '1' then
				b_dout <= ram(to_integer(unsigned(b_addr))) after g.delay;
			else
				b_dout <= (others => '0') after g.delay;
			end if;
		end if;
	end process;
end architecture;
 
--- Single Port Model ---
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.util.all;
 
entity single_port_block_ram is
	generic (g: common_generics;
		addr_length: positive    := 12;
		data_length: positive    := 16;
		file_name:   string      := "memory.bin";
		file_type:   file_format := FILE_BINARY);
	port (
		clk:  in  std_ulogic;
		dwe:  in  std_ulogic;
		dre:  in  std_ulogic;
		addr: in  std_ulogic_vector(addr_length - 1 downto 0);
		din:  in  std_ulogic_vector(data_length - 1 downto 0);
		dout: out std_ulogic_vector(data_length - 1 downto 0) := (others => '0'));
end entity;
 
architecture behav of single_port_block_ram is
	constant ram_size: positive := 2 ** addr_length;
 
	type ram_type is array ((ram_size - 1 ) downto 0) of std_ulogic_vector(data_length - 1 downto 0);
 
	impure function initialize_ram(the_file_name: in string; the_file_type: in file_format) return ram_type is
		variable ram_data:   ram_type;
		file     in_file:    text is in the_file_name;
		variable input_line: line;
		variable tmp:        bit_vector(data_length - 1 downto 0);
		variable c:          character;
		variable slv:        std_ulogic_vector(data_length - 1 downto 0);
	begin
		for i in 0 to ram_size - 1 loop
			if the_file_type = FILE_NONE then
				ram_data(i):=(others => '0');
			elsif not endfile(in_file) then
				readline(in_file,input_line);
				if the_file_type = FILE_BINARY then
					read(input_line, tmp);
					ram_data(i) := std_ulogic_vector(to_stdlogicvector(tmp));
				elsif the_file_type = FILE_HEX then -- hexadecimal
					assert (data_length mod 4) = 0 report "(data_length%4)!=0" severity failure;
					for j in 1 to (data_length/4) loop
						c:= input_line((data_length/4) - j + 1);
						slv((j*4)-1 downto (j*4)-4) := hex_char_to_std_ulogic_vector_tb(c);
					end loop;
					ram_data(i) := slv;
				else
					report "Incorrect file type given: " & file_format'image(the_file_type) severity failure;
				end if;
			else
				ram_data(i) := (others => '0');
			end if;
		end loop;
		file_close(in_file);
		return ram_data;
	end function;
 
	shared variable ram: ram_type := initialize_ram(file_name, file_type);
 
begin
	block_ram: process(clk)
	begin
		if rising_edge(clk) then
			if dwe = '1' then
				ram(to_integer(unsigned(addr))) := din;
			end if;
 
			if dre = '1' then
				dout <= ram(to_integer(unsigned(addr))) after g.delay;
			else
				dout <= (others => '0') after g.delay;
			end if;
		end if;
	end process;
end architecture;
 
------------------------- Single and Dual Port Block RAM ----------------------------
 
------------------------- Data Source -----------------------------------------------
--|
--| This module spits out a bunch of data.
--|
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.single_port_block_ram;
use work.util.counter;
use work.util.all;
 
entity data_source is
	generic (g: common_generics;
		addr_length: positive    := 12;
		data_length: positive    := 16;
		file_name:   string      := "memory.bin";
		file_type:   file_format := FILE_BINARY);
	port (
		clk:     in  std_ulogic;
		rst:     in  std_ulogic;
 
		ce:      in  std_ulogic := '1';
		cr:      in  std_ulogic;
 
		load:    in  std_ulogic_vector(addr_length - 1 downto 0) := (others => '0');
		load_we: in  std_ulogic := '0';
 
		dout:    out std_ulogic_vector(data_length - 1 downto 0));
end entity;
 
architecture structural of data_source is
	signal addr: std_ulogic_vector(addr_length - 1 downto 0);
begin
	count: work.util.counter
		generic map (g => g, N => addr_length)
		port map (
			clk      =>  clk,
			rst      =>  rst,
			ce       =>  ce,
			cr       =>  cr,
			dout     =>  addr,
			load_i   =>  load,
			load_we  =>  load_we);
 
	ram: work.util.single_port_block_ram
		generic map (
			g           => g,
			addr_length => addr_length,
			data_length => data_length,
			file_name   => file_name,
			file_type   => file_type)
		port map (
			clk  => clk,
			addr => addr,
			dwe  => '0',
			dre  => '1',
			din  => (others => '0'),
			dout => dout);
 
end architecture;
 
------------------------- Data Source -----------------------------------------------
 
------------------------- uCPU ------------------------------------------------------
-- @brief An incredible simple microcontroller
-- @license MIT
-- @author Richard James Howe
-- @copyright Richard James Howe (2017)
--
-- Based on:
-- https://stackoverflow.com/questions/20955863/vhdl-microprocessor-microcontroller
--
--  INSTRUCTION    CYCLES 87 6543210 OPERATION
--  ADD WITH CARRY   2    00 ADDRESS A = A   + MEM[ADDRESS]
--  NOR              2    01 ADDRESS A = A NOR MEM[ADDRESS]
--  STORE            1    10 ADDRESS MEM[ADDRESS] = A
--  JCC              1    11 ADDRESS IF(CARRY) { PC = ADDRESS, CLEAR CARRY }
--
-- It would be interesting to make a customizable CPU in which the
-- instructions could be customized based upon what. Another interesting
-- possibility is making a simple assembler purely in VHDL, which should
-- be possible, but difficult. A single port version would require another
-- state to fetch the operand and another register, or more states.
--
-- I have another small, bit serial CPU, available at:
--   <https://github.com/howerj/bit-serial>
-- Which is also tiny, but potentially much more useful. It is currently in
-- a state of flux though.
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity ucpu is
	generic (
		asynchronous_reset:  boolean := true;  -- use asynchronous reset if true, synchronous if false
		delay:               time    := 0 ns; -- simulation only
 
		width:               positive range 8 to 32 := 8);
	port (
		clk, rst: in  std_ulogic;
 
		pc:       out std_ulogic_vector(width - 3 downto 0);
		op:       in  std_ulogic_vector(width - 1 downto 0);
 
		adr:      out std_ulogic_vector(width - 3 downto 0);
		di:       in  std_ulogic_vector(width - 1 downto 0);
		re, we:   out std_ulogic;
		do:       out std_ulogic_vector(width - 1 downto 0));
end entity;
 
architecture rtl of ucpu is
	signal a_c,   a_n:       unsigned(di'high + 1 downto di'low) := (others => '0'); -- accumulator
	signal pc_c,  pc_n:      unsigned(pc'range)                  := (others => '0');
	signal alu:              std_ulogic_vector(1 downto 0)       := (others => '0');
	signal state_c, state_n: std_ulogic                          := '0'; -- FETCH/Single cycle instruction or EXECUTE
begin
	pc          <= std_ulogic_vector(pc_n) after delay;
	do          <= std_ulogic_vector(a_c(do'range)) after delay;
	alu         <= op(op'high downto op'high - 1) after delay;
	adr         <= op(adr'range) after delay;
	we          <= '1' when alu = "10" else '0' after delay; -- STORE
	re          <= alu(1) nor state_c after delay;           -- FETCH for ADD and NOR
	state_n     <= alu(1) nor state_c after delay;           -- FETCH not taken or FETCH done
	pc_n        <= unsigned(op(pc_n'range)) when (alu = "11"    and a_c(a_c'high) = '0') else -- Jump when carry set
			pc_c                    when (state_c = '0' and alu(1) = '0')        else -- FETCH
			(pc_c + 1) after delay; -- EXECUTE
 
	process(clk, rst)
	begin
		if rst = '1' and asynchronous_reset then
			a_c     <= (others => '0') after delay;
			pc_c    <= (others => '0') after delay;
			state_c <= '0' after delay;
		elsif rising_edge(clk) then
			if rst = '1' and not asynchronous_reset then
				a_c     <= (others => '0') after delay;
				pc_c    <= (others => '0') after delay;
				state_c <= '0' after delay;
			else
				a_c     <= a_n after delay;
				pc_c    <= pc_n after delay;
				state_c <= state_n after delay;
			end if;
		end if;
	end process;
 
	process(op, alu, di, a_c, state_c)
	begin
		a_n     <= a_c after delay;
 
		if alu = "11" and a_c(a_c'high) = '0' then a_n(a_n'high) <= '0' after delay; end if;
 
		if state_c = '1' then -- EXECUTE for ADD and NOR
			assert alu(1) = '0' severity failure;
			if alu(0) = '0' then a_n <= '0' & a_c(di'range) + unsigned('0' & di) after delay; end if;
			if alu(0) = '1' then a_n <= a_c nor '0' & unsigned(di) after delay; end if;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.util.all;
 
entity ucpu_tb is
	generic (g: common_generics;
		file_name: string := "ucpu.bin");
end entity;
 
architecture testing of ucpu_tb is
	constant clock_period:      time     := 1000 ms / g.clock_frequency;
 
	constant data_length: positive := 8;
	constant addr_length: positive := data_length - 2;
 
	signal a_addr: std_ulogic_vector(addr_length - 1 downto 0);
	signal a_dout: std_ulogic_vector(data_length - 1 downto 0) := (others => '0');
 
	signal b_dwe:  std_ulogic;
	signal b_dre:  std_ulogic;
	signal b_addr: std_ulogic_vector(addr_length - 1 downto 0);
	signal b_din:  std_ulogic_vector(data_length - 1 downto 0);
	signal b_dout: std_ulogic_vector(data_length - 1 downto 0) := (others => '0');
 
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	bram_0: entity work.dual_port_block_ram
	generic map (g => g,
		addr_length => addr_length,
		data_length => data_length,
		file_name   => file_name,
		file_type   => FILE_BINARY)
	port map (
		a_clk   =>  clk,
		a_dwe   =>  '0',
		a_dre   =>  '1',
		a_addr  =>  a_addr,
		a_din   =>  (others => '0'),
		a_dout  =>  a_dout,
 
		b_clk   =>  clk,
		b_dwe   =>  b_dwe,
		b_dre   =>  b_dre,
		b_addr  =>  b_addr,
		b_din   =>  b_din,
		b_dout  =>  b_dout);
 
	ucpu_0: entity work.ucpu
	generic map (delay => g.delay, width => data_length)
	port map (
		clk => clk,
		rst => rst,
		pc  => a_addr,
		op  => a_dout,
 
		re  => b_dre,
		we  => b_dwe,
		di  => b_dout,
		do  => b_din,
		adr => b_addr);
 
	stimulus_process: process
	begin
		wait for clock_period * 1000;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- uCPU ------------------------------------------------------
 
------------------------- Restoring Division ----------------------------------------
--
-- Computes a/b in N cycles
--
-- https://en.wikipedia.org/wiki/Division_algorithm#Restoring_division
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity restoring_divider is
	generic (g: common_generics; N: positive);
	port (
		clk:   in  std_ulogic;
		rst:   in  std_ulogic := '0';
 
		a:     in  std_ulogic_vector(N - 1 downto 0);
		b:     in  std_ulogic_vector(N - 1 downto 0);
		start: in  std_ulogic;
		done:  out std_ulogic;
		c:     out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture rtl of restoring_divider is
	signal a_c, a_n: unsigned(a'range) := (others => '0');
	signal b_c, b_n: unsigned(b'range) := (others => '0');
	signal m_c, m_n: unsigned(b'range) := (others => '0');
	signal o_c, o_n: unsigned(c'range) := (others => '0');
	signal e_c, e_n: std_ulogic         := '0';
	signal count_c, count_n: unsigned(work.util.n_bits(N) downto 0) := (others => '0');
begin
	c <= std_ulogic_vector(o_n);
 
	process(clk, rst)
		procedure reset is
		begin
			a_c      <=  (others  =>  '0') after g.delay;
			b_c      <=  (others  =>  '0') after g.delay;
			m_c      <=  (others  =>  '0') after g.delay;
			o_c      <=  (others  =>  '0') after g.delay;
			e_c      <=  '0' after g.delay;
			count_c  <=  (others  =>  '0') after g.delay;
		end procedure;
	begin
		if rst = '1' and g.asynchronous_reset then
			reset;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				reset;
			else
				a_c      <=  a_n after g.delay;
				b_c      <=  b_n after g.delay;
				m_c      <=  m_n after g.delay;
				o_c      <=  o_n after g.delay;
				e_c      <=  e_n after g.delay;
				count_c  <=  count_n after g.delay;
			end if;
		end if;
	end process;
 
	divide: process(a, b, start, a_c, b_c, m_c, e_c, o_c, count_c)
		variable m_v: unsigned(b'range) := (others => '0');
	begin
		done     <=  '0';
		a_n      <=  a_c;
		b_n      <=  b_c;
		m_v      :=  m_c;
		e_n      <=  e_c;
		o_n      <=  o_c;
		count_n  <=  count_c;
		if start = '1' then
			a_n   <= unsigned(a) after g.delay;
			b_n   <= unsigned(b) after g.delay;
			m_v   := (others => '0');
			e_n   <= '1' after g.delay;
			o_n   <= (others => '0') after g.delay;
			count_n <= (others => '0') after g.delay;
		elsif e_c = '1' then
			if count_c(count_c'high) = '1' then
				done  <= '1' after g.delay;
				e_n   <= '0' after g.delay;
				o_n   <= a_c after g.delay;
				count_n <= (others => '0') after g.delay;
			else
				m_v(b'high downto 1) := m_v(b'high - 1 downto 0);
				m_v(0) := a_c(a'high);
				a_n(a'high downto 1) <= a_c(a'high - 1 downto 0) after g.delay;
				m_v := m_v - b_c;
				if m_v(m_v'high) = '1' then
					m_v := m_v + b_c;
					a_n(0) <= '0' after g.delay;
				else
					a_n(0) <= '1' after g.delay;
				end if;
				count_n <= count_c + 1 after g.delay;
			end if;
		else
			count_n <= (others => '0') after g.delay;
		end if;
		m_n <= m_v after g.delay;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.util.common_generics;
 
entity restoring_divider_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of restoring_divider_tb is
	constant clock_period: time     := 1000 ms / g.clock_frequency;
	constant N:          positive := 8;
 
	signal a: std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal b: std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal c: std_ulogic_vector(N - 1 downto 0) := (others => '0');
	signal start, done: std_ulogic := '0';
 
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.restoring_divider
		generic map (g => g, N => N)
		port map (
			clk   => clk,
			rst   => rst,
			a     => a,
			b     => b,
			start => start,
			done  => done,
			c     => c);
 
	stimulus_process: process
	begin
		wait for clock_period * 2;
 
		a <= x"64";
		b <= x"0A";
		start <= '1';
		wait for clock_period * 1;
		start <= '0';
		wait until done = '1';
		--assert c = x"0A" severity failure;
 
		wait for clock_period * 10;
		b     <= x"05";
		start <= '1';
		wait for clock_period * 1;
		start <= '0';
		wait until done = '1';
		--assert c = x"14" severity failure;
 
		stop <= '1';
		wait;
	end process;
 
end architecture;
------------------------- Restoring Divider ---------------------------------------------------
 
------------------------- Debouncer -----------------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity debounce_us is
	generic (g: common_generics; timer_period_us: natural);
	port (
		clk:   in  std_ulogic;
		di:    in  std_ulogic;
		do:    out std_ulogic := '0');
end entity;
 
architecture rtl of debounce_us is
	signal ff: std_ulogic_vector(1 downto 0) := (others => '0');
	signal rst, done: std_ulogic             := '0';
begin
	timer: work.util.timer_us
		generic map (g => g, timer_period_us => timer_period_us)
		port map (
			clk => clk,
			rst => rst,
			co  => done);
 
	process(clk)
	begin
		if rising_edge(clk) then
			ff(0) <= di    after g.delay;
			ff(1) <= ff(0) after g.delay;
			if (ff(0) xor ff(1)) = '1' then
				rst <= '1' after g.delay;
			elsif done = '1' then
				do  <= ff(1) after g.delay;
			else
				rst   <= '0';
			end if;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity debounce_us_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of debounce_us_tb is
	constant clock_period: time := 1000 ms / g.clock_frequency;
 
	signal di,  do:  std_ulogic := '0';
	signal clk, rst: std_ulogic := '0';
	signal stop:     std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: work.util.debounce_us
		generic map (g => g, timer_period_us => 1)
		port map (clk => clk, di => di, do => do);
 
	stimulus_process: process
	begin
		wait for clock_period * 2;
		di <= '1';
 
		wait for 1.5 us;
 
		stop <= '1';
		wait;
	end process;
end architecture;
------------------------- Debouncer -----------------------------------------------------------
 
------------------------- Debouncer Block -----------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity debounce_block_us is
	generic (g: common_generics; N: positive; timer_period_us: natural);
	port (
		clk:   in  std_ulogic;
		di:    in  std_ulogic_vector(N - 1 downto 0);
		do:    out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture structural of debounce_block_us is
begin
	debouncer: for i in (N - 1) downto 0 generate
		d_instance: work.util.debounce_us
			generic map (g => g, timer_period_us => timer_period_us)
			port map (clk => clk, di => di(i), do => do(i));
	end generate;
end architecture;
 
------------------------- Debouncer Block -----------------------------------------------------
 
------------------------- State Changed -------------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity state_changed is
	generic (g: common_generics);
	port (
		clk: in  std_ulogic;
		rst: in  std_ulogic;
		di:  in  std_ulogic;
		do:  out std_ulogic);
end entity;
 
architecture rtl of state_changed is
	signal state_c, state_n: std_ulogic_vector(1 downto 0) := (others => '0');
begin
	process(clk, rst)
	begin
		if rst = '1' and g.asynchronous_reset then
			state_c <= (others => '0') after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				state_c <= (others => '0') after g.delay;
			else
				state_c <= state_n after g.delay;
			end if;
		end if;
	end process;
 
	do <= '1' when (state_c(0) xor state_c(1)) = '1' else '0';
 
	process(di, state_c)
	begin
		state_n(0) <= state_c(1) after g.delay;
		state_n(1) <= di after g.delay;
	end process;
end architecture;
 
------------------------- Change State --------------------------------------------------------
 
------------------------- Change State Block --------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.common_generics;
 
entity state_block_changed is
	generic (g: common_generics; N: positive);
	port (
		clk: in  std_ulogic;
		rst: in  std_ulogic;
		di:  in  std_ulogic_vector(N - 1 downto 0);
		do:  out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture structural of state_block_changed is
begin
	changes: for i in (N - 1) downto 0 generate
		d_instance: work.util.state_changed
			generic map (g => g)
			port map (clk => clk, rst => rst, di => di(i), do => do(i));
	end generate;
end architecture;
 
------------------------- Change State Block --------------------------------------------------
 
------------------------- Reset Signal Generator ----------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity reset_generator is
	generic (g: common_generics; reset_period_us:  natural := 0);
	port (
		clk: in  std_logic := 'X';
		rst: out std_logic := '0'); -- reset out!
end entity;
 
architecture behavior of reset_generator is
	constant cycles:  natural := (g.clock_frequency / 1000000) * reset_period_us;
	subtype  counter is unsigned(max(1, n_bits(cycles) - 1) downto 0);
	signal   c_c, c_n: counter := (others => '0');
begin
	process (clk)
	begin
		if rising_edge(clk) then
			c_c <= c_n after g.delay;
		end if;
	end process;
 
	process (c_c)
	begin
		if c_c = (cycles - 1) then
			c_n <= c_c after g.delay;
			rst <= '0' after g.delay;
		else
			c_n <= c_c + 1 after g.delay;
			rst <= '1' after g.delay;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.common_generics;
 
entity reset_generator_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of reset_generator_tb is
	constant clock_period:      time     := 1000 ms / g.clock_frequency;
	signal stop, clk, rst: std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => open);
 
	uut: entity work.reset_generator
		generic map (g => g, reset_period_us => 1)
		port map (clk => clk, rst => rst);
 
	stimulus_process: process
	begin
		wait for clock_period;
		assert rst = '1' severity failure;
		wait for 1 us;
		assert rst = '0' severity failure;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- Reset Signal Generator ----------------------------------------------
 
------------------------- Bit Count -----------------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.n_bits;
use work.util.common_generics;
 
entity bit_count is
	generic (g: common_generics; N: positive);
	port (
		bits:   in std_ulogic_vector(N - 1 downto 0);
		count: out std_ulogic_vector(n_bits(N) downto 0));
end entity;
 
architecture behavior of bit_count is
begin
	process (bits)
		constant zero: unsigned(count'high - 1 downto count'low)  := (others => '0');
		variable t: unsigned(count'range) := (others => '0');
	begin
		t := (others => '0');
		for i in bits'low to bits'high loop
			t := t + (zero & bits(i));
		end loop;
		count <= std_ulogic_vector(t) after g.delay;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.n_bits;
use work.util.common_generics;
 
entity bit_count_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of bit_count_tb is
	constant clock_period: time     := 1000 ms / g.clock_frequency;
	constant N:            positive := 3;
	signal bits:  std_ulogic_vector(N - 1 downto 0)     := (others => '0');
	signal count: std_ulogic_vector(n_bits(N) downto 0) := (others => '0');
begin
	uut: entity work.bit_count
		generic map (g => g, N => N)
		port map (bits => bits, count => count);
 
	stimulus_process: process
		procedure test(b: std_ulogic_vector; c: std_ulogic_vector)  is
		begin
			bits <= b;
			wait for clock_period;
			assert count = c severity failure;
		end procedure;
	begin
		test("000", "000");
		test("001", "001");
		test("010", "001");
		test("011", "010");
		test("100", "001");
		test("101", "010");
		test("110", "010");
		test("111", "011");
		wait;
	end process;
 
end architecture;
 
------------------------- Bit Count -----------------------------------------------------------
 
------------------------- Majority Voter ------------------------------------------------------
--
-- NB. This could be constructed from a more generic 'assert output if bit
-- count greater than N' module.
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity majority is
	generic (g: common_generics; N: positive; even_wins: boolean := false);
	port (
		bits: in std_ulogic_vector(N - 1 downto 0);
		vote: out std_ulogic;
		tie:  out std_ulogic);
end entity;
 
architecture behavior of majority is
	signal count: std_ulogic_vector(n_bits(N) downto 0) := (others => '0');
	-- It might be worth handling up to five or so bits in combinatorial
	-- logic, or it might not.
begin
	majority_1: if N = 1 generate
		vote <= bits(0) after g.delay;
		tie  <= '0' after g.delay;
	end generate;
 
	majority_2: if N = 2 generate
		ew_2:  if     even_wins generate vote <= bits(0)  or bits(1) after g.delay; end generate;
		enw_2: if not even_wins generate vote <= bits(0) and bits(1) after g.delay; end generate;
		tie  <= bits(0) or bits(1);
	end generate;
 
	majority_3: if N = 3 generate
		vote <= (bits(0) and bits(1)) or (bits(1) and bits(2)) or (bits(0) and bits(2)) after g.delay;
		tie  <= '0' after g.delay;
	end generate;
 
	majority_n: if N > 3 generate
		bit_counter: entity work.bit_count
			generic map (g => g, N => N)
			port map (bits => bits, count => count);
 
		tie <= '1' when (unsigned(count) = N/2) and (N mod 2) = 0 else '0' after g.delay;
 
		process (count)
		begin
			if even_wins and (N mod 2) = 0 then
				if unsigned(count) >= (N/2) then
					vote <= '1' after g.delay;
				else
					vote <= '0' after g.delay;
				end if;
			else
				if unsigned(count) > (N/2) then
					vote <= '1' after g.delay;
				else
					vote <= '0' after g.delay;
				end if;
			end if;
		end process;
	end generate;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity majority_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of majority_tb is
	constant clock_period: time := 1000 ms / g.clock_frequency;
 
	constant N_3:    positive := 3;
	constant N_4_t:  positive := 4;
	constant N_4_m:  positive := 4;
 
	signal bits_3:   std_ulogic_vector(N_3   - 1 downto 0) := (others => '0');
	signal bits_4_t: std_ulogic_vector(N_4_t - 1 downto 0) := (others => '0');
	signal bits_4_m: std_ulogic_vector(N_4_m - 1 downto 0) := (others => '0');
 
	signal vote_3,     tie_3: std_ulogic := '0';
	signal vote_4_t, tie_4_t: std_ulogic := '0';
	signal vote_4_m, tie_4_m: std_ulogic := '0';
begin
	uut_3: entity work.majority
		generic map (g => g, N => N_3)
		port map (bits => bits_3, vote => vote_3, tie => tie_3);
 
	uut_4_t: entity work.majority
		generic map (g => g, N => N_4_t, even_wins => true)
		port map (bits => bits_4_t, vote => vote_4_t, tie => tie_4_t);
 
	uut_4_m: entity work.majority
		generic map (g => g, N => N_4_m)
		port map (bits => bits_4_m, vote => vote_4_m, tie => tie_4_m);
 
	stimulus_process: process
		procedure test_3(b: std_ulogic_vector; vote, tie: std_ulogic)  is
		begin
			bits_3 <= b;
			wait for clock_period;
			assert vote_3 = vote and tie_3 = tie severity failure;
		end procedure;
 
		procedure test_4_t(b: std_ulogic_vector; vote, tie: std_ulogic)  is
		begin
			bits_4_t <= b;
			wait for clock_period;
			assert vote_4_t = vote and tie_4_t = tie severity failure;
		end procedure;
 
		procedure test_4_m(b: std_ulogic_vector; vote, tie: std_ulogic)  is
		begin
			bits_4_m <= b;
			wait for clock_period;
			assert vote_4_m = vote and tie_4_m = tie severity failure;
		end procedure;
	begin
		test_3("000", '0', '0');
		test_3("001", '0', '0');
		test_3("010", '0', '0');
		test_3("011", '1', '0');
		test_3("100", '0', '0');
		test_3("101", '1', '0');
		test_3("110", '1', '0');
		test_3("111", '1', '0');
 
		test_4_t("0000", '0', '0');
		test_4_t("0001", '0', '0');
		test_4_t("0010", '0', '0');
		test_4_t("0011", '1', '1');
		test_4_t("0100", '0', '0');
		test_4_t("0101", '1', '1');
		test_4_t("0110", '1', '1');
		test_4_t("0111", '1', '0');
		test_4_t("1000", '0', '0');
		test_4_t("1001", '1', '1');
		test_4_t("1010", '1', '1');
		test_4_t("1011", '1', '0');
		test_4_t("1100", '1', '1');
		test_4_t("1101", '1', '0');
		test_4_t("1110", '1', '0');
		test_4_t("1111", '1', '0');
 
		test_4_m("0000", '0', '0');
		test_4_m("0001", '0', '0');
		test_4_m("0010", '0', '0');
		test_4_m("0011", '0', '1');
		test_4_m("0100", '0', '0');
		test_4_m("0101", '0', '1');
		test_4_m("0110", '0', '1');
		test_4_m("0111", '1', '0');
		test_4_m("1000", '0', '0');
		test_4_m("1001", '0', '1');
		test_4_m("1010", '0', '1');
		test_4_m("1011", '1', '0');
		test_4_m("1100", '0', '1');
		test_4_m("1101", '1', '0');
		test_4_m("1110", '1', '0');
		test_4_m("1111", '1', '0');
 
		wait;
	end process;
end architecture;
 
------------------------- Majority Voter ------------------------------------------------------
 
------------------------- Delay Line ----------------------------------------------------------
-- 'DEPTH' * clock period delay line. Minimum delay of 0.
--
-- NB. It would be possible to create a delay line that would allow you to delay samples by
-- varying amounts with a FIFO and a counter, which is sort of line a Run
-- Length Compression Decoder. A sample and a counter would be pushed to the
-- FIFO, the delay line mechanism would pull a sample/counter and hold the value
-- for that amount of time.
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity delay_line is
	generic (g: common_generics; width: positive; depth: natural);
	port (
		clk:  in std_ulogic;
		rst:  in std_ulogic;
		ce:   in std_ulogic := '1';
		di:   in std_ulogic_vector(width - 1 downto 0);
		do:  out std_ulogic_vector(width - 1 downto 0));
end entity;
 
architecture behavior of delay_line is
	type delay_line_t is array(integer range 0 to depth) of std_ulogic_vector(di'range);
	signal sigs: delay_line_t := (others => (others => '0'));
begin
	sigs(0) <= di;
	delay_line_generate: for i in 0 to depth generate
		rest: if i > 0 generate
			ux: work.util.reg
				generic map (g => g, N => width)
				port map (clk => clk, rst => rst, we => ce, di => sigs(i - 1), do => sigs(i));
		end generate;
	end generate;
	do <= sigs(depth);
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity delay_line_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of delay_line_tb is
	constant clock_period:  time     := 1000 ms / g.clock_frequency;
	constant depth:       natural    := 2;
	constant width:       positive   := 8;
	signal clk, rst:      std_ulogic := '0';
	signal stop:          std_ulogic := '0';
 
	signal di, do: std_ulogic_vector(width - 1 downto 0) := (others => '0');
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut: entity work.delay_line
	generic map (g => g, depth => depth, width => width) port map (clk => clk, rst => rst, di => di, do => do, ce => '1');
 
	stimulus_process: process
	begin
		-- put a bit into the shift register and wait
		-- for it to come out the other size
		wait until rst = '0';
		di <= x"AA";
		wait for clock_period * 1;
		di <= x"55";
		wait for clock_period * 1;
		di <= x"CC";
		wait for clock_period * 1;
		di <= x"DD";
		assert do = x"AA" severity failure;
		wait for clock_period * 1;
		di <= x"00";
		assert do = x"55" severity failure;
		wait for clock_period * 1;
		assert do = x"CC" severity failure;
		wait for clock_period * 1;
		assert do = x"DD" severity failure;
		wait for clock_period * 1;
		assert do = x"00" severity failure;
		stop <= '1';
		wait;
	end process;
end architecture;
 
------------------------- Delay Line ----------------------------------------------------------
 
------------------------- Gray CODEC ----------------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity gray_encoder is
	generic (g: common_generics; N: positive);
	port (di: in std_ulogic_vector(N - 1 downto 0);
	     do: out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture behavior of gray_encoder is
begin
	gry: for i in N - 1 downto 0 generate
		first: if i = (N - 1) generate
			do(i) <= di(i);
		end generate;
 
		rest: if i < (N - 1) generate
			do(i) <= di(i + 1) xor di(i);
		end generate;
	end generate;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity gray_decoder is
	generic (g: common_generics; N: positive);
	port (di: in std_ulogic_vector(N - 1 downto 0);
	     do: out std_ulogic_vector(N - 1 downto 0));
end entity;
 
architecture behavior of gray_decoder is
begin
	gry: for i in N - 1 downto 0 generate
		first: if i = (N - 1) generate
			do(i) <= di(i) after g.delay;
		end generate;
 
		rest: if i < (N - 1) generate
			do(i) <= parity(di(N - 1 downto i), true) after g.delay;
		end generate;
	end generate;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity gray_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of gray_tb is
	constant clock_period:  time     := 1000 ms / g.clock_frequency;
	constant n:           positive   := 3;
	signal clk, rst:      std_ulogic := '0';
	signal stop:          std_ulogic := '0';
 
	signal di, gray, do: std_ulogic_vector(n - 1 downto 0) := (others => '0');
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut_encode: entity work.gray_encoder
	generic map (g => g, n => n) port map (di => di, do => gray);
 
	uut_decode: entity work.gray_decoder
	generic map (g => g, n => n) port map (di => gray, do => do);
 
	stimulus_process: process
		procedure test(slv: std_ulogic_vector) is
		begin
			di <= slv;
			wait for clock_period * 1;
			assert di = do severity failure;
			wait for clock_period * 1;
		end procedure;
	begin
		di <= (others => '0');
		wait until rst = '0';
		test("000");
		test("001");
		test("010");
		test("011");
		test("100");
		test("101");
		test("110");
		test("111");
		stop <= '1';
		wait;
	end process;
end architecture;
 
------------------------- Gray CODEC ----------------------------------------------------------
 
------------------------- Parity Module -------------------------------------------------------
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity parity_module is
	generic (g: common_generics; N: positive; even: boolean);
	port (di: in std_ulogic_vector(N - 1 downto 0); do: out std_ulogic);
end entity;
 
architecture behavior of parity_module is
begin
	do <= parity(di, even) after g.delay;
end architecture;
 
------------------------- Parity Module -------------------------------------------------------
 
------------------------- Hamming CODEC  ------------------------------------------------------
-- This is a Hamming encoder/decoder, with an extra parity bit. This can be used for error
-- correction and detection across a noisy line.
--
-- See <https://en.wikipedia.org/wiki/Hamming_code> for more information.
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity hamming_7_4_encoder is
	generic (g: common_generics);
	port (
		di:      in std_ulogic_vector(3 downto 0);
		do:     out std_ulogic_vector(6 downto 0);
		parity: out std_ulogic);
end entity;
 
architecture behavior of hamming_7_4_encoder is
	signal p1, p2, p3: std_ulogic := '0';
begin
	p1 <= di(0) xor di(1) xor di(3) after g.delay;
	p2 <= di(0) xor di(2) xor di(3) after g.delay;
	p3 <= di(1) xor di(2) xor di(3) after g.delay;
	do(0) <= p1    after g.delay;
	do(1) <= p2    after g.delay;
	do(2) <= di(0) after g.delay;
	do(3) <= p3    after g.delay;
	do(4) <= di(1) after g.delay;
	do(5) <= di(2) after g.delay;
	do(6) <= di(3) after g.delay;
	parity <= p1 xor p2 xor p3 xor di(0) xor di(1) xor di(2) xor di(3);
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity hamming_7_4_decoder is
	generic (g: common_generics; secdec: boolean := true);
	port (
		di:      in std_ulogic_vector(6 downto 0);
		parity:  in std_ulogic;
		do:     out std_ulogic_vector(3 downto 0);
		single, double: out std_ulogic);
end entity;
 
architecture behavior of hamming_7_4_decoder is
	signal s:  std_ulogic_vector(2 downto 0) := (others => '0');
	signal co, ct, dip: std_ulogic_vector(di'high + 1 downto 0)   := (others => '0');
	signal cp: std_ulogic := '0';
	signal unequal: std_ulogic := '0';
begin
	s(2) <= di(3) xor di(4) xor di(5) xor di(6) after g.delay;
	s(1) <= di(1) xor di(2) xor di(5) xor di(6) after g.delay;
	s(0) <= di(0) xor di(2) xor di(4) xor di(6) after g.delay;
 
	do(0) <= co(2) after g.delay;
	do(1) <= co(4) after g.delay;
	do(2) <= co(5) after g.delay;
	do(3) <= co(6) after g.delay;
 
	cp <= '0' when not secdec else di(0) xor di(1) xor di(2) xor di(3) xor di(4) xor di(5) xor di(6) after g.delay;
 
	dip(dip'high) <= parity when secdec else '0' after g.delay;
	dip(di'range) <= di after g.delay;
 
	unequal <= '1' when ct(di'range) /= dip(di'range) else '0' after g.delay;
 
	process (dip, s)
	begin
		ct <= dip after g.delay;
		ct(to_integer(unsigned(s) - 1)) <= not dip(to_integer(unsigned(s) - 1)) after g.delay;
	end process;
 
	process (s, dip, parity, ct, cp, unequal)
	begin
		co <= dip;
		single <= '0';
		double <= '0';
 
		if secdec and parity /= cp then
			if unequal = '1' then
				single <= '1';
				co <= ct;
			end if;
		else
			if unequal = '1' then
				if secdec then
					double <= '1';
				else
					single <= '1';
					co <= ct;
				end if;
			end if;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
 
entity hamming_7_4_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of hamming_7_4_tb is
	constant clock_period:  time     := 1000 ms / g.clock_frequency;
	constant n:           positive   := 3;
	signal clk, rst:      std_ulogic := '0';
	signal stop:          std_ulogic := '0';
	signal di, do, do_s: std_ulogic_vector(3 downto 0) := (others => '0');
	signal encoded_tx, encoded_rx, ebit: std_ulogic_vector(6 downto 0) := (others => '0');
	signal parity, single, double: std_ulogic := '0';
	signal parity_s, single_s, double_s: std_ulogic := '0';
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut_encode: entity work.hamming_7_4_encoder
	generic map (g => g) port map (di => di, do => encoded_tx, parity => parity);
 
	lossy_channel: process(encoded_tx)
		variable seed1, seed2: positive;
		variable r: real;
		variable i: integer range 0 to 7;
	begin
		encoded_rx    <= encoded_tx;
		uniform(seed1, seed2, r);
		if r > 0.5 then
			uniform(seed1, seed2, r);
			i := integer(floor(r * 6.99));
			encoded_rx(i) <= not encoded_tx(i);
			uniform(seed1, seed2, r);
		end if;
		if r > 0.5 then
			uniform(seed1, seed2, r);
			i := integer(floor(r * 6.99));
			encoded_rx(i) <= not encoded_tx(i);
		end if;
	end process;
 
	ebit <= encoded_tx xor encoded_rx;
 
	uut_decode_secdec: entity work.hamming_7_4_decoder
	generic map (g => g) port map (di => encoded_rx, do => do, parity => parity, single => single, double => double);
 
	uut_decode_single: entity work.hamming_7_4_decoder
	generic map (g => g, secdec => false) port map (di => encoded_rx, do => do_s, parity => parity_s, single => single_s, double => double_s);
 
	stimulus_process: process
		procedure test(slv: std_ulogic_vector) is
		begin
			di <= slv;
			wait for clock_period * 2;
			if bit_count_f(ebit) = 2 then
				assert double = '1' severity failure;
				assert single = '0' severity failure;
			elsif bit_count_f(ebit) = 1 then
				assert di = do severity failure;
				assert single = '1' severity failure;
				assert double = '0' severity failure;
 
				assert di = do_s severity failure;
				assert single_s = '1' severity failure;
			else
				assert di = do severity failure;
				assert double = '0' severity failure;
				assert single = '0' severity failure;
 
				assert di = do_s severity failure;
				assert single_s = '0' severity failure;
			end if;
			wait for clock_period * 2;
		end procedure;
		variable ii: unsigned(7 downto 0) := (others => '1');
	begin
		di <= (others => '0');
		wait until rst = '0';
 
		while ii /= x"00" loop
			ii := ii - 1;
			test(std_ulogic_vector(ii(3 downto 0)));
		end loop;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- Hamming CODEC  ------------------------------------------------------
 
------------------------- VGA Controller ------------------------------------------------------
-- VGA Controller
--
-- See:
--  * <https://en.wikipedia.org/wiki/Video_Graphics_Array>
--  * <http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/1998_w/Altera_UP1_Board_Map/vga.html>
--  * <https://www.digikey.com/eewiki/pages/viewpage.action?pageId=15925278>
--
-- This purpose of this VGA controller is to provide the necessary VGA
-- timings for a given resolution (which has to be determined at instantiation
-- time and cannot be configured on the fly). The VGA controller will generate
-- the correct HSYNC and VSYNCH signals needed, as well as the current row and
-- column that is currently being drawn. This can be used to generate an image.
--
-- Example timing for 640 x 480:
--
--  |-----800 pixels / 31.778 us---------|
--  |-----640 Pixels--------|-160 Pixels-|
--  |-----25.422 us---------|--5.75 us---|
--  
--  +-----------------------+------------+   VSYNC
--  |                       |         ^  |     |
--  |                       |         |  |     |
--  |                       |         |  |     |
--  |    Display Period     |   480 Rows |     |
--  |                       |         |  |     |
--  |                       |         |  |     |
--  |                       |         |  |     |
--  |                       |         v  |     |
--  +-----------------------+        --- |     |
--  |                                 ^  |    _| <- Front porch 0.318 ms (10 rows)
--  |                                 |  |   |
--  |      Blanking Period       45 Rows |   | <--- VSYNC pulse 0.064 ms (2 rows)
--  |                                 |  |   |_
--  |                                 v  |     | <- Back porch 1.048 ms (33 rows)
--  +------------------------------------+     |
--                                 
--                              ___
--  ___________________________|   |_____ HSYNC
--                            ^  ^  ^
--  0.636 us   Front Porch __/  /  / <-(16 pixels)
--  3.813 us   HSYNC Pulse ____/  /  <-(96 pixels)
--  1.907 us   Back Porch _______/   <-(48 pixels)
--
 
library ieee, work;
use ieee.std_logic_1164.all;
use work.util.all;
 
entity vga_controller is
	generic (
		g: common_generics;
		pixel_clock_frequency:  positive := 25_000_000;
		constant cfg: vga_configuration  := vga_640x480);
	port (
		clk, rst:          in std_ulogic;
		h_sync, v_sync:   out std_ulogic;
		h_blank, v_blank: out std_ulogic;
		column, row:      out integer);
end entity;
 
architecture behavior of vga_controller is
	constant h_period: integer := cfg.h_pulse + cfg.h_back_porch + cfg.h_pixels + cfg.h_front_porch; -- number of pixel clocks in a row
	constant v_period: integer := cfg.v_pulse + cfg.v_back_porch + cfg.v_pixels + cfg.v_front_porch; -- number of rows in column
	signal h_sync_internal, v_sync_internal: std_ulogic := '0';
begin
	-- The clock does not need to be exactly the correct value
	assert pixel_clock_frequency <= (cfg.clock_frequency + 250_000)
		and pixel_clock_frequency >= (cfg.clock_frequency - 250_000) severity warning;
 
	h_sync <= h_sync_internal xor cfg.h_polarity;
	v_sync <= v_sync_internal xor cfg.v_polarity;
 
	process (clk, rst)
		constant h_start: integer := cfg.h_pixels + cfg.h_front_porch;
		constant h_end:   integer := cfg.h_pixels + cfg.h_front_porch + cfg.h_pulse;
		constant v_start: integer := cfg.v_pixels + cfg.v_front_porch;
		constant v_end:   integer := cfg.v_pixels + cfg.v_front_porch + cfg.v_pulse;
		variable h_count: integer range 0 to h_period - 1 := 0;  -- horizontal counter (counts the columns)
		variable v_count: integer range 0 to v_period - 1 := 0;  -- vertical counter (counts the rows)
		procedure reset is
		begin
			h_count         := 0;
			v_count         := 0;
			h_blank         <= '0' after g.delay;
			v_blank         <= '0' after g.delay;
			column          <= 0 after g.delay;
			row             <= 0 after g.delay;
		end procedure;
	begin
		if rst = '1' and g.asynchronous_reset then
			reset;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				reset;
			else
				if h_count < (h_period - 1) then -- pixel count
					h_count := h_count + 1;
				else
					if v_count < (v_period - 1) then -- row count
						v_count := v_count + 1;
					else
						v_count := 0;
					end if;
					h_count := 0;
				end if;
 
				h_sync_internal <= not logical((h_count < h_start) or (h_count >= h_end)) after g.delay;
				v_sync_internal <= not logical((v_count < v_start) or (v_count >= v_end)) after g.delay;
 
				column <= cfg.h_pixels - 1;
				row    <= cfg.v_pixels - 1;
 
				if h_count < cfg.h_pixels then h_blank <= '0'; column <= h_count; else h_blank <= '1'; end if;
				if v_count < cfg.v_pixels then v_blank <= '0'; row    <= v_count; else v_blank <= '1'; end if;
			end if;
		end if;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity vga_tb is
	generic (g: common_generics; pixel_clock_frequency: positive := 25_000_000; simulation_us: time := 20000 us);
end entity;
 
architecture testing of vga_tb is
	constant pixel_clock_period: time   := 1000 ms / pixel_clock_frequency;
	signal rst, clk:        std_ulogic  := '1';
	signal stop:            boolean     := false;
	signal h_sync, v_sync:  std_ulogic  := 'X';
	signal h_blank, v_blank: std_ulogic := 'X';
	signal column, row:     integer     := 0;
begin
	duration: process begin wait for simulation_us; stop <= true; wait; end process;
	clk_process: process
	begin
		rst <= '1';
		wait for pixel_clock_period * 5;
		rst <= '0';
		while not stop loop
			clk <= '1';
			wait for pixel_clock_period / 2;
			clk <= '0';
			wait for pixel_clock_period / 2;
		end loop;
		wait;
	end process;
 
	uut: work.util.vga_controller
		generic map (g => g, pixel_clock_frequency => pixel_clock_frequency)
		port map (
			rst     => rst,
			clk     => clk,
			h_sync  => h_sync,
			v_sync  => v_sync,
			h_blank => h_blank,
			v_blank => v_blank,
			column  => column,
			row     => row);
end architecture;
 
------------------------- VGA Controller ------------------------------------------------------
 
------------------------- LED Controller ------------------------------------------------------
--| This module implements a 7 segment display (plus decimal point) driver,
--|  with 4 displays in total:
--|
--|    _____________________ an (selects segment)
--|    |     |     |     |
--|   __    __    __    __
--|  |  |  |  |  |  |  |  |
--|  |__|  |__|  |__|  |__|
--|  |  |  |  |  |  |  |  |
--|  |__|. |__|. |__|. |__|.
--|   |____|_____|_____|____ ka (value to display on segment)
--|
--| Each of the display shares a common anode for all of its LEDs, this can be
--| used to select an individual display
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity led_7_segment_display is
	generic (
		g:                      common_generics;
		use_bcd_not_hex:        boolean  := true;
		refresh_rate_us:        natural  := 1500;
		number_of_led_displays: positive := 4);
	port (
		clk:      in   std_ulogic;
		rst:      in   std_ulogic;
 
		leds_we:  in   std_ulogic;
		leds:     in   std_ulogic_vector((number_of_led_displays * led_7_segment_character_length) - 1 downto 0);
 
		-- Physical outputs
		an:       out  std_ulogic_vector(number_of_led_displays - 1 downto 0);  -- anodes, controls on/off
		ka:       out  std_ulogic_vector(7 downto 0)); -- cathodes, data on display
end;
 
architecture rtl of led_7_segment_display is
 
	-- This lookup table converts a BCD character into a value
	-- that can be displayed on an 7 segment display. The layout of which
	-- is as follows:
	--
	--       A
	--      ---
	--   F |   | B
	--     |___|
	--   E | G | C
	--     |___| . DP
	--       D
	--
	-- The following encoding is used to convert the input BCD character
	-- into a value that can be put onto the display.
	--
	--  -----------------------------------------
	-- |   | DP| G | F | E | D | C | B | A | Hex |
	-- |BCD| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |Hi Lo|
	--  -----------------------------------------
	-- | 0 |   |   | 1 | 1 | 1 | 1 | 1 | 1 | 3 F |
	-- | 1 |   |   |   |   |   | 1 | 1 |   | 0 6 |
	-- | 2 |   | 1 |   | 1 | 1 |   | 1 | 1 | 5 B |
	-- | 3 |   | 1 |   |   | 1 | 1 | 1 | 1 | 4 F |
	-- | 4 |   | 1 | 1 |   |   | 1 | 1 |   | 6 6 |
	-- | 5 |   | 1 | 1 |   | 1 | 1 |   | 1 | 6 D |
	-- | 6 |   | 1 | 1 | 1 | 1 | 1 |   | 1 | 7 D |
	-- | 7 |   |   |   |   |   | 1 | 1 | 1 | 0 7 |
	-- | 8 |   | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 F |
	-- | 9 |   | 1 | 1 |   | 1 | 1 | 1 | 1 | 6 F |
	-- |   |   |   |   |   |   |   |   |   | 0 0 |
	-- | . | 1 |   |   |   |   |   |   |   | 8 0 |
	-- | - |   | 1 |   |   |   |   |   |   | 4 0 |
	--  -----------------------------------------
	-- | A |   | 1 | 1 | 1 |   | 1 | 1 | 1 | 7 7 |
	-- | b |   | 1 | 1 | 1 | 1 | 1 |   |   | 7 C |
	-- | C |   |   | 1 | 1 | 1 |   |   | 1 | 3 9 |
	-- | d |   | 1 |   | 1 | 1 | 1 | 1 |   | 5 E |
	-- | E |   | 1 | 1 | 1 | 1 |   |   | 1 | 7 9 |
	-- | F |   | 1 | 1 | 1 |   |   |   | 1 | 7 1 |
	--  -----------------------------------------
	--
	-- The table is then inverted before it goes to the output.
	--
 
	function hex_to_7_segment(a: led_7_segment_character) return led_7_segment is
		variable r: std_ulogic_vector(7 downto 0) := x"00";
	begin
		-- NB. You may have thought a case statement would be the best
		-- way of doing this, and you would be right. However, Xilinx ISE
		-- issues annoying 'INFO' statements when you do - thanks Xilinx!
		-- The generated hardware is the same however.
		   if a = "0000" then r := x"3F"; -- 0
		elsif a = "0001" then r := x"06"; -- 1
		elsif a = "0010" then r := x"5B"; -- 2
		elsif a = "0011" then r := x"4F"; -- 3
		elsif a = "0100" then r := x"66"; -- 4
		elsif a = "0101" then r := x"6D"; -- 5
		elsif a = "0110" then r := x"7D"; -- 6
		elsif a = "0111" then r := x"07"; -- 7
		elsif a = "1000" then r := x"7F"; -- 8
		elsif a = "1001" then r := x"6F"; -- 9
		elsif a = "1010" then r := x"77"; -- A
		elsif a = "1011" then r := x"7C"; -- b
		elsif a = "1100" then r := x"39"; -- C
		elsif a = "1101" then r := x"5E"; -- d
		elsif a = "1110" then r := x"79"; -- E
		elsif a = "1111" then r := x"71"; -- F
		end if;
		return r;
	end function;
 
	function bcd_to_7_segment(a: led_7_segment_character) return led_7_segment is
		variable r: std_ulogic_vector(7 downto 0) := x"00";
	begin
		case a is
			when "0000" => r := x"3F"; -- 0
			when "0001" => r := x"06"; -- 1
			when "0010" => r := x"5B"; -- 2
			when "0011" => r := x"4F"; -- 3
			when "0100" => r := x"66"; -- 4
			when "0101" => r := x"6D"; -- 5
			when "0110" => r := x"7D"; -- 6
			when "0111" => r := x"07"; -- 7
			when "1000" => r := x"7F"; -- 8
			when "1001" => r := x"6F"; -- 9
			when "1010" => r := x"00"; -- Blank
			when "1011" => r := x"80"; -- .
			when "1100" => r := x"40"; -- -
			when "1101" => r := x"00"; -- Unused
			when "1110" => r := x"00"; -- Unused
			when "1111" => r := x"00"; -- Unused
			when others => r := x"00"; -- Unused
		end case;
		return r;
	end function;
 
	function char_to_7_segment(a: led_7_segment_character) return led_7_segment is
	begin
		if use_bcd_not_hex then
			return invert(bcd_to_7_segment(a));
		else
			return invert(hex_to_7_segment(a));
		end if;
	end function;
 
	signal leds_o: std_ulogic_vector(leds'range) := (others => '0');
 
	signal do_shift:  std_ulogic := '0';
	signal shift_reg: std_ulogic_vector(number_of_led_displays - 1 downto 0);
 
	signal leds_reg_o: std_ulogic_vector(leds'range) := (others => '0');
	signal leds_reg_we_o: std_ulogic := '0';
begin
	an <= invert(shift_reg) after g.delay;
 
	segment_reg: entity work.reg
		generic map (g => g, N => number_of_led_displays * led_7_segment_character_length)
		port map (
			clk => clk,
			rst => rst,
			we  => leds_we,
			di  => leds,
			do  => leds_reg_o);
 
	segment_reg_re: entity work.reg
		generic map (g => g, N => 1)
		port map (
			clk   => clk,
			rst   => rst,
			we    => '1',
			di(0) => leds_we,
			do(0) => leds_reg_we_o);
 
	led_gen: for i in number_of_led_displays - 1 downto 0 generate
		led_i: entity work.reg
			generic map (g => g, N => led_7_segment_character_length)
			port map (
				clk => clk,
				rst => rst,
				we  => leds_reg_we_o,
				di  => leds_reg_o((i*led_7_segment_character_length) + led_7_segment_character_length - 1 downto (i*led_7_segment_character_length)),
				do  => leds_o((i*led_7_segment_character_length) + led_7_segment_character_length - 1 downto (i*led_7_segment_character_length)));
	end generate;
 
	timer: entity work.timer_us
		generic map (g => g, timer_period_us => refresh_rate_us)
		port map (
			clk             => clk,
			rst             => rst,
			co              => do_shift);
 
	process(rst, clk, do_shift, shift_reg)
	begin
		if rst = '1' and g.asynchronous_reset then
			shift_reg    <= (others => '0') after g.delay;
			shift_reg(0) <= '1' after g.delay;
		elsif rising_edge(clk) then
			if rst = '1' and not g.asynchronous_reset then
				shift_reg    <= (others => '0') after g.delay;
				shift_reg(0) <= '1' after g.delay;
			else
				if do_shift = '1' then
					shift_reg <= shift_reg(number_of_led_displays - 2 downto 0) & shift_reg(number_of_led_displays - 1) after g.delay;
				else
					shift_reg <= shift_reg after g.delay;
				end if;
			end if;
		end if;
	end process;
 
	process(leds_o, shift_reg)
	begin
		ka <= (others => '0');
		for i in  number_of_led_displays - 1 downto 0 loop
			if '1' = shift_reg(number_of_led_displays - i - 1) then
				ka <= char_to_7_segment(leds_o(i*led_7_segment_character_length + led_7_segment_character_length - 1 downto (i*led_7_segment_character_length))) after g.delay;
			end if;
		end loop;
	end process;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity led_7_segment_display_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of led_7_segment_display_tb is
	constant clock_period:  time     := 1000 ms / g.clock_frequency;
	signal clk, rst:      std_ulogic := '0';
	signal stop:          std_ulogic := '0';
 
	constant number_of_led_displays: positive := 4;
	signal an: std_ulogic_vector(number_of_led_displays - 1 downto 0);
	signal ka: std_ulogic_vector(7 downto 0);
	signal leds_we: std_ulogic;
	signal leds:    std_ulogic_vector((number_of_led_displays * led_7_segment_character_length) - 1 downto 0);
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	-- We have a very fast refresh rate here, just for testing purposes.
	uut: entity work.led_7_segment_display
		generic map (g => g, refresh_rate_us => 1)
		port map (clk => clk, rst => rst, leds_we => leds_we, leds => leds, an => an, ka => ka);
 
	stimulus_process: process
	begin
		wait for clock_period * 2;
		leds_we <= '1';
		leds <= x"1234";
		wait for clock_period * 1;
		leds_we <= '0';
		wait for clock_period * 1000;
		stop <= '1';
		wait;
	end process;
 
end architecture;
 
------------------------- LED Controller ------------------------------------------------------
 
------------------------- Sine / Cosine  ------------------------------------------------------
-- Sine / Cosine calculation using multiplication
-- Half-inched from <https://github.com/jamesbowman/sincos>
-- Angles are input as signed Furmans (1 Furman = (1/pow(2, 16) of a circle))
-- 1 Degree is ~182 Furmans. 1 rad is ~10430 Furmans.
-- Result is signed scaled 16-bit integer; -1 = -32767, +1 = 32767
--
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity sine is
	generic (g: common_generics; pipeline: boolean := true);
	port (
		clk, rst, xwe: in std_ulogic;
		x:  in  std_ulogic_vector(15 downto 0);
		s:  out std_ulogic_vector(15 downto 0));
end entity;
 
architecture behavior of sine is
	subtype val is signed(x'range);
	subtype mul is signed((val'high * 2) + 1 downto 0);
	function half_multiply_add(a, b, c: val) return val is
		variable t: mul;
		variable r: val;
	begin
		t := a * b;
		r := t(t'high downto r'high + 1) + c;
		return r;
	end function;
	signal n: signed(2 downto 0);
	signal xn, z, y, sums, sumc, sum1, cc,  t0, t1, t0n, t1n, sa, so: val;
	signal cc32: mul;
	signal xnslv, t0nslv, t1nslv: std_ulogic_vector(x'range);
begin
	pipe_0: if pipeline generate
		reg_in: work.util.reg
			generic map (g => g, N => x'length)
			port map (clk => clk, rst => rst, we => xwe, di => x, do => xnslv);
		reg_out: work.util.reg
			generic map (g => g, N => x'length)
			port map (clk => clk, rst => rst, we => '1', di => std_ulogic_vector(so), do => s);
		xn  <= signed(xnslv);
		t0n <= signed(t0); -- also need to delay n
		t1n <= signed(t1); -- also need to delay n
	end generate;
	no_pipe_0: if not pipeline generate
		xn <= signed(x);
		s  <= std_ulogic_vector(so) after g.delay;
		t0n <= t0;
		t1n <= t1;
	end generate;
 
	y(1 downto 0)  <= (others => '0') after g.delay;
	y(15 downto 2) <= signed(xn(13 downto 0)) after g.delay;
	n    <= signed(xn(15 downto 13)) + "01" after g.delay;
	z    <= half_multiply_add(y, y,        x"0000") after g.delay;
	sumc <= half_multiply_add(z, x"0FBD", -x"4EE9") after g.delay;
	sums <= half_multiply_add(z, x"04F8", -x"2953") after g.delay;
	sum1 <= half_multiply_add(z, sums,     x"6487") after g.delay;
	t0   <= z    when n(1) = '1' else y after g.delay;
	t1   <= sumc when n(1) = '1' else sum1 after g.delay;
	cc32 <= t0n * t1n after g.delay;
	cc   <= cc32(cc32'high - 1 downto cc'high) after g.delay;
	sa   <= cc + x"7FFF" when n(1) = '1' else cc after g.delay;
	so   <= -sa when n(2) = '1' else sa after g.delay;
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity cosine is
	generic (g: common_generics; pipeline: boolean := true);
	port (
		clk, rst, xwe: in std_ulogic;
		x:  in  std_ulogic_vector(15 downto 0);
		c:  out std_ulogic_vector(15 downto 0));
end entity;
 
architecture behavior of cosine is
	signal xn: std_ulogic_vector(c'range);
begin
	xn <= std_ulogic_vector(signed(x) + x"4000");
	calc: entity work.sine
		generic map(g => g, pipeline => pipeline) port map(clk => clk, rst => rst, xwe => xwe, x => xn, s => c);
end architecture;
 
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.util.all;
 
entity sine_tb is
	generic (g: common_generics);
end entity;
 
architecture testing of sine_tb is
	constant clock_period:  time     := 1000 ms / g.clock_frequency;
	signal clk, rst:      std_ulogic := '0';
	signal stop:          std_ulogic := '0';
 
	constant number_of_led_displays: positive := 4;
	signal x: std_ulogic_vector(15 downto 0);
	signal s, c: std_ulogic_vector(x'range);
begin
	cs: entity work.clock_source_tb
		generic map (g => g, hold_rst => 2)
		port map (stop => stop, clk => clk, rst => rst);
 
	uut_c: entity work.sine   generic map (g => g) port map (clk => clk, rst => rst, xwe => '1', x => x, s => s);
	uut_s: entity work.cosine generic map (g => g) port map (clk => clk, rst => rst, xwe => '1', x => x, c => c);
 
	stimulus_process: process
		variable cnt: integer := -32768;
	begin
		x <= std_ulogic_vector(to_signed(cnt, x'length));
		wait for clock_period * 2;
		while cnt < 32768 loop
			x <= std_ulogic_vector(to_signed(cnt, x'length));
			wait for clock_period * 10;
			cnt := cnt + 182;
		end loop;
		stop <= '1';
		wait;
	end process;
end architecture;
------------------------- Sine / Cosine  ------------------------------------------------------
 
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.