URL
https://opencores.org/ocsvn/forth-cpu/forth-cpu/trunk
Subversion Repositories forth-cpu
[/] [forth-cpu/] [trunk/] [util.vhd] - Rev 3
Go to most recent revision | 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 is the function n_bits). --| @author Richard James Howe --| @copyright Copyright 2017 Richard James Howe --| @license MIT --| @email howe.r.j.89@gmail.com ------------------------------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use std.textio.all; package util is component util_tb is generic(clock_frequency: positive); end component; component clock_source_tb is generic(clock_frequency: positive; hold_rst: positive := 1); port( stop: in std_ulogic := '0'; clk: buffer std_ulogic; clk_with_jitter: out std_ulogic := '0'; rst: out std_ulogic := '0'); end component; component reg generic(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(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(clock_frequency: positive); end component; component timer_us generic(clock_frequency: positive; timer_period_us: natural); port( clk: in std_ulogic; rst: in std_ulogic; co: out std_ulogic); end component; component timer_us_tb generic(clock_frequency: positive); end component; component rising_edge_detector is 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(clock_frequency: positive); end component; component rising_edge_detectors is generic(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; -- @note half_adder test bench is folded in to full_adder_tb component half_adder is port( a: in std_ulogic; b: in std_ulogic; sum: out std_ulogic; carry: out std_ulogic); end component; component full_adder is 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(clock_frequency: positive); end component; component fifo is generic (data_width: positive; fifo_depth: positive); 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(clock_frequency: positive); end component; component counter is generic( 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(clock_frequency: positive); end component; component lfsr is generic(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 to tap'low); do: out std_ulogic_vector(tap'high + 1 to tap'low)); end component; component lfsr_tb is generic(clock_frequency: positive); end component; component io_pins is generic( 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 component; component io_pins_tb is generic(clock_frequency: positive); end component; type file_format is (FILE_HEX, FILE_BINARY, FILE_NONE); component dual_port_block_ram is generic(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(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(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(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( clock_frequency: positive; file_name: string := "ucpu.bin"); end component; component restoring_divider is generic(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(clock_frequency: positive); end component; component debounce_us is generic(clock_frequency: positive; 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(N: positive; clock_frequency: 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(clock_frequency: positive); end component; component state_changed is 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(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; function max(a: natural; b: natural) return natural; function min(a: natural; b: natural) return natural; function n_bits(x: natural) return natural; function n_bits(x: std_ulogic_vector) 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 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 hex_char_to_std_ulogic_vector(hc: character) return std_ulogic_vector; function to_std_ulogic_vector(s: string) 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; --- Not synthesizable --- 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); 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 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 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 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 hex_char_to_std_ulogic_vector(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; -- <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; --- Not synthesizable --- -- 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; end; ------------------------- Utility Test Bench ---------------------------------------- library ieee; use ieee.std_logic_1164.all; use work.util.all; entity util_tb is generic(clock_frequency: positive); end entity; architecture behav of util_tb is begin uut_shiftReg: work.util.shift_register_tb generic map(clock_frequency => clock_frequency); uut_timer_us: work.util.timer_us_tb generic map(clock_frequency => clock_frequency); uut_full_add: work.util.full_adder_tb generic map(clock_frequency => clock_frequency); uut_fifo: work.util.fifo_tb generic map(clock_frequency => clock_frequency); uut_counter: work.util.counter_tb generic map(clock_frequency => clock_frequency); uut_lfsr: work.util.lfsr_tb generic map(clock_frequency => clock_frequency); uut_ucpu: work.util.ucpu_tb generic map(clock_frequency => clock_frequency); uut_rdivider: work.util.restoring_divider_tb generic map(clock_frequency => clock_frequency); uut_debounce: work.util.debounce_us_tb generic map(clock_frequency => clock_frequency); uut_rising_edge_detector: work.util.rising_edge_detector_tb generic map(clock_frequency => clock_frequency); 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 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; -- n_bits(x: std_ulogic_vector) return natural; -- mux(a, b : std_ulogic_vector) return std_ulogic; wait; end process; end architecture; ------------------------- Function Test Bench --------------------------------------- ------------------------- Test bench clock source ----------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; entity clock_source_tb is generic(clock_frequency: positive; hold_rst: positive := 1); port( stop: in std_ulogic := '0'; clk: buffer 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 / clock_frequency; signal jitter_delay: time := 0 ns; signal jitter_clk: std_ulogic := '0'; begin clk_process: process variable seed1, seed2 : positive; variable r : real; begin while stop = '0' loop uniform(seed1, seed2, r); jitter_delay <= r * 1 ns; clk <= '1'; wait for clock_period / 2; clk <= '0'; wait for clock_period / 2; end loop; wait; end process; clk_with_jitter <= transport clk after jitter_delay; 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; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity reg is generic( 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; process(rst, clk) begin if rst = '1' then r_c <= (others => '0'); elsif rising_edge(clk) then r_c <= r_n; end if; end process; process(r_c, di, we) begin r_n <= r_c; if we = '1' then r_n <= di; end if; end process; end; ------------------------- Generic Register of std_ulogic_vector ---------------------- ------------------------- Shift register -------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; -- https://stackoverflow.com/questions/36342960/optional-ports-in-vhdl entity shift_register is generic(N: positive); port ( clk: in std_ulogic; rst: in std_ulogic; we: in std_ulogic; di: in std_ulogic; do: out std_ulogic; 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' then r_c <= (others => '0'); elsif rising_edge(clk) then r_c <= r_n; end if; end process; process(r_c, di, we, load_i, load_we) begin if load_we = '1' then r_n <= load_i; else r_n <= "0" & r_c(N - 1 downto 1); if we = '1' then r_n(N-1) <= di; end if; end if; end process; end; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity shift_register_tb is generic(clock_frequency: positive); end entity; architecture behav of shift_register_tb is constant N: positive := 8; constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.shift_register generic map(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; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.util.max; use work.util.n_bits; entity timer_us is generic( clock_frequency: positive; timer_period_us: positive := 1); port( clk: in std_ulogic := 'X'; rst: in std_ulogic := 'X'; co: out std_ulogic := '0'); end timer_us; architecture rtl of timer_us is constant cycles: natural := (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' then c_c <= (others => '0'); elsif rising_edge(clk) then c_c <= c_n; end if; end process; process (c_c) begin if c_c = (cycles - 1) then c_n <= (others => '0'); co <= '1'; else c_n <= c_c + 1; co <= '0'; end if; end process; end; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity timer_us_tb is generic(clock_frequency: positive); end; architecture behav of timer_us_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.timer_us generic map(clock_frequency => clock_frequency, 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; use ieee.std_logic_1164.all; entity rising_edge_detector is 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' then sin_0 <= '0'; sin_1 <= '0'; elsif rising_edge(clk) then sin_0 <= di; sin_1 <= sin_0; end if; end process; do <= not sin_1 and sin_0; end architecture; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity rising_edge_detector_tb is generic(clock_frequency: positive); end; architecture behav of rising_edge_detector_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.rising_edge_detector 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; use ieee.std_logic_1164.all; entity rising_edge_detectors is generic(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 port map(clk => clk, rst => rst, di => di(i), do => do(i)); end generate; end architecture; ------------------------- Rising Edge Detector -------------------------------------- ------------------------- Half Adder ------------------------------------------------ library ieee; use ieee.std_logic_1164.all; entity half_adder is 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; carry <= a and b; end architecture; ------------------------- Half Adder ------------------------------------------------ ------------------------- Full Adder ------------------------------------------------ library ieee; use ieee.std_logic_1164.all; entity full_adder is 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 port map(a => x, b => y, sum => sum1, carry => carry1); ha2: entity work.half_adder port map(a => sum1, b => z, sum => sum, carry => carry2); carry <= carry1 or carry2; end architecture; library ieee; use ieee.std_logic_1164.all; entity full_adder_tb is generic(clock_frequency: positive); end entity; architecture behav of full_adder_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.full_adder 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 ------------------------------------------------------ -- Originally from http://www.deathbylogic.com/2013/07/vhdl-standard-fifo/ -- @copyright Public Domain -- @todo Add more comments about the FIFOs origin, add assertions test -- synthesis, make this more generic (with empty FIFO and FIFO count signals) -- -- The code can be used freely and appears to be public domain, comment -- from author is: "You can use any code posted here freely, there is no copyright." -- -- @note The FIFO has been modified from the original to bring it in line with -- this projects coding standards. library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity fifo is generic( data_width: positive; fifo_depth: positive); port( clk: in std_ulogic; rst: in std_ulogic; we: in std_ulogic; di: in std_ulogic_vector (data_width - 1 downto 0); re: in std_ulogic; do: out std_ulogic_vector (data_width - 1 downto 0); empty: out std_ulogic := '1'; full: out std_ulogic := '0'); end fifo; architecture behavioral of fifo is begin -- memory pointer process fifo_proc: process (clk, rst) type fifo_memory is array (0 to fifo_depth - 1) of std_ulogic_vector (data_width - 1 downto 0); variable memory: fifo_memory; variable head: natural range 0 to fifo_depth - 1; variable tail: natural range 0 to fifo_depth - 1; variable looped: boolean; begin if rst = '1' then head := 0; tail := 0; looped := false; full <= '0'; empty <= '1'; do <= (others => '0'); elsif rising_edge(clk) then do <= (others => '0'); if re = '1' then if looped = true or head /= tail then -- update data output do <= memory(tail); -- update tail pointer as needed if tail = fifo_depth - 1 then tail := 0; looped := false; else tail := tail + 1; end if; end if; end if; if we = '1' then if looped = false or head /= tail then -- write data to memory memory(head) := di; -- increment head pointer as needed if head = fifo_depth - 1 then head := 0; looped := true; else head := head + 1; end if; end if; end if; -- update empty and full flags if head = tail then if looped then full <= '1'; else empty <= '1'; end if; else empty <= '0'; full <= '0'; end if; end if; end process; end architecture; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity fifo_tb is generic(clock_frequency: positive); end entity; architecture behavior of fifo_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, 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(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; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity counter is generic( 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); 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); process(clk, rst) begin if rst = '1' then c_c <= (others => '0'); elsif rising_edge(clk) then c_c <= c_n; 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); else if cr = '1' then c_n <= (others => '0'); elsif ce = '1' then c_n <= c_c + 1; end if; end if; end process; end architecture; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity counter_tb is generic(clock_frequency: positive); end entity; architecture behavior of counter_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.counter generic map( 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 be improved a lot, and it could also be used to -- calculate CRCs, which are basically the same computation. Its interface -- is not the best either, being a free running counter. -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity lfsr is generic(constant tap: std_ulogic_vector); port ( clk: in std_ulogic; rst: in std_ulogic; we: in std_ulogic; ce: in std_ulogic := '1'; 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; process(rst, clk) begin if rst = '1' then r_c <= (others => '0'); elsif rising_edge(clk) then r_c <= r_n; end if; end process; process(r_c, di, we, ce) begin if we = '1' then r_n <= di; 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); else r_n(i) <= r_c(i+1); end if; end loop; else r_n <= r_c; end if; end process; end architecture; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity lfsr_tb is generic(clock_frequency: positive); end entity; architecture behavior of lfsr_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.lfsr generic map(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 ---------------------------------------- -- @todo Test this in hardware -- -- 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; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity io_pins is generic( 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(N => N) port map(clk => clk, rst => rst, di => control, we => control_we, do => control_o); din_r: entity work.reg generic map(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'; pins(i) <= din_o(i) when control_o(i) = '1' else 'Z'; end generate; end architecture; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity io_pins_tb is generic(clock_frequency: positive); end entity; architecture behavior of io_pins_tb is constant clock_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.io_pins generic map(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; 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 -- -- @todo Read in actual binary data files, see: https://stackoverflow.com/questions/14173652 -- -- 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(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(file_name: in string; file_type: in file_format) return ram_type is variable ram_data: ram_type; file in_file: text is in 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 file_type = FILE_NONE then ram_data(i):=(others => '0'); elsif not endfile(in_file) then readline(in_file,input_line); if file_type = FILE_BINARY then read(input_line, tmp); ram_data(i) := std_ulogic_vector(to_stdlogicvector(tmp)); elsif 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(c); end loop; ram_data(i) := slv; else report "Incorrect file type given: " & file_format'image(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))); else a_dout <= (others => '0'); 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))); else b_dout <= (others => '0'); end if; end if; end process; end architecture; --- Single Port Model --- library ieee; 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(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(file_name: in string; file_type: in file_format) return ram_type is variable ram_data: ram_type; file in_file: text is in 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 file_type = FILE_NONE then ram_data(i):=(others => '0'); elsif not endfile(in_file) then readline(in_file,input_line); if file_type = FILE_BINARY then read(input_line, tmp); ram_data(i) := std_ulogic_vector(to_stdlogicvector(tmp)); elsif 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(c); end loop; ram_data(i) := slv; else report "Incorrect file type given: " & file_format'image(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))); else dout <= (others => '0'); end if; end if; end process; end architecture; ------------------------- Single and Dual Port Block RAM ---------------------------- ------------------------- Data Source ----------------------------------------------- --| --| This module spits out a bunch of data --| --| @todo Create a single module that can be used to capture and replay data at --| a configurable rate. This could be used as a logger or as a waveform --| generator. Depending on the generics used this should synthesize to either --| logger, or a data source, or both. A pre-divider could also be supplied as --| generic options, to lower the clock rate. --| 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(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( 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( 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. -- -- @todo Test in hardware, document, make assembler, and a project that -- just contains an instantiation of this core. -- library ieee,work; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity ucpu is generic(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); do <= std_ulogic_vector(a_c(do'range)); alu <= op(op'high downto op'high - 1); adr <= op(adr'range); we <= '1' when alu = "10" else '0'; -- STORE re <= alu(1) nor state_c; -- FETCH for ADD and NOR state_n <= alu(1) nor state_c; -- 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; -- EXECUTE process(clk, rst) begin if rst = '1' then a_c <= (others => '0'); pc_c <= (others => '0'); state_c <= '0'; elsif rising_edge(clk) then a_c <= a_n; pc_c <= pc_n; state_c <= state_n; end if; end process; process(op, alu, di, a_c, state_c) begin a_n <= a_c; if alu = "11" and a_c(a_c'high) = '0' then a_n(a_n'high) <= '0'; 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); end if; if alu(0) = '1' then a_n <= a_c nor '0' & unsigned(di); 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( clock_frequency: positive; file_name: string := "ucpu.bin"); end entity; architecture testing of ucpu_tb is constant clk_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); bram_0: entity work.dual_port_block_ram generic map( 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(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 clk_period * 1000; stop <= '1'; wait; end process; end architecture; ------------------------- uCPU ------------------------------------------------------ ------------------------- Restoring Division ---------------------------------------- -- @todo Add remainder to output, rename signals, make a -- better test bench, add non-restoring division, and describe module -- -- 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; entity restoring_divider is generic(N: positive); port( clk: in std_ulogic; rst: in std_ulogic := '0'; a: in unsigned(N - 1 downto 0); b: in unsigned(N - 1 downto 0); start: in std_ulogic; done: out std_ulogic; c: out unsigned(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 <= o_n; process(clk, rst) begin if rst = '1' then a_c <= (others => '0'); b_c <= (others => '0'); m_c <= (others => '0'); o_c <= (others => '0'); e_c <= '0'; count_c <= (others => '0'); elsif rising_edge(clk) then a_c <= a_n; b_c <= b_n; m_c <= m_n; o_c <= o_n; e_c <= e_n; count_c <= count_n; 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 <= a; b_n <= b; m_v := (others => '0'); e_n <= '1'; o_n <= (others => '0'); count_n <= (others => '0'); elsif e_c = '1' then if count_c(count_c'high) = '1' then done <= '1'; e_n <= '0'; o_n <= a_c; count_n <= (others => '0'); 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); m_v := m_v - b_c; if m_v(m_v'high) = '1' then m_v := m_v + b_c; a_n(0) <= '0'; else a_n(0) <= '1'; end if; count_n <= count_c + 1; end if; else count_n <= (others => '0'); end if; m_n <= m_v; end process; end architecture; library ieee,work; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; entity restoring_divider_tb is generic(clock_frequency: positive); end entity; architecture testing of restoring_divider_tb is constant clk_period: time := 1000 ms / clock_frequency; constant N: positive := 8; signal a: unsigned(N - 1 downto 0) := (others => '0'); signal b: unsigned(N - 1 downto 0) := (others => '0'); signal c: unsigned(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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: entity work.restoring_divider generic map(N => N) port map( clk => clk, rst => rst, a => a, b => b, start => start, done => done, c => c); stimulus_process: process begin wait for clk_period * 2; a <= x"64"; b <= x"0A"; start <= '1'; wait for clk_period * 1; start <= '0'; wait until done = '1'; --assert c = x"0A" severity failure; wait for clk_period * 10; b <= x"05"; start <= '1'; wait for clk_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; entity debounce_us is generic(clock_frequency: positive; 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( clock_frequency => clock_frequency, 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; ff(1) <= ff(0); rst <= '0'; if (ff(0) xor ff(1)) = '1' then rst <= '1'; elsif done = '1' then do <= ff(1); end if; end if; end process; end architecture; library ieee,work; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity debounce_us_tb is generic(clock_frequency: positive); end entity; architecture testing of debounce_us_tb is constant clk_period: time := 1000 ms / 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(clock_frequency => clock_frequency, hold_rst => 2) port map(stop => stop, clk => clk, rst => rst); uut: work.util.debounce_us generic map(clock_frequency => clock_frequency, timer_period_us => 1) port map(clk => clk, di => di, do => do); stimulus_process: process begin wait for clk_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; entity debounce_block_us is generic(N: positive; clock_frequency: 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( clock_frequency => clock_frequency, 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; entity state_changed is 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' then state_c <= (others => '0'); elsif rising_edge(clk) then state_c <= state_n; 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); state_n(1) <= di; end process; end architecture; ------------------------- Change State -------------------------------------------------------- ------------------------- Change State Block -------------------------------------------------- library ieee,work; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity state_block_changed is generic(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 port map(clk => clk, rst => rst, di => di(i), do => do(i)); end generate; end architecture; ------------------------- Change State Block --------------------------------------------------
Go to most recent revision | Compare with Previous | Blame | View Log