Monday, June 20, 2011

Easy way of implementing registers

Hi,

I got frustrated writing processor accessed registers once again in a very traditional way, and decided to make it more convenient. This is the result, so far:

In VHDL, array indexes are typically integers with ranges, e.g. std_logic_vector(15 to 27), but in fact every constrained type can be used as an index:

    type mytype is (something, used, as_index, values);
    type myarray is array (mytype) of integer range 5 to 9;

This defines an array type, myarray, which has 4 entries, each being integer from 5 to 9.

You will then access the arrray by utilizing mytype type of variable/signal as index:

    signal storage: myarray;
...
    variable i: mytype;
...
    myarray(i) <= myarray(i) + 1;

The key here is that the index type has to be constrained, otherwise the size of array is infinite which can not be supported.

And don't be too tempted to write something like this:

    type myarray2 is array (std_logic_vector(7 downto 0)) of std_logic_vector(7 downto 0);

It works, but not as you would think in the first place.
std_logic is defined to have 9 different values (U,X,0,1,Z,W,L,H,-) and every combination of 8 of them will do, so instead of 2^8 entries, it will yield to 9^8 entries. The size of that array will be over 43 million bytes.

Anyway, this array indexing scheme proved to be quite powerful for register implementation. Especially if one needs to modify the design often, or if there are a lots of registers. Any comments appreciated...

BR, -Topi

registers.vhdl:
***********************
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.registers_pkg.all;

entity registers is
    port(
        reset_in: in std_logic;
        clk_in: in std_logic;
       
        address_in: in reg_add_type;
        writedata_in: in reg_val_type;
        readdata_out: out reg_val_type;
        write_en_in: in std_logic;
        read_en_in: in std_logic;
        regs_out: out reg_array
    );
end;

architecture rtl of registers is
    signal regs: reg_array;
begin
    regs_out <= regs;

    -- register writes / modifications:
    process(reset_in, clk_in)
        variable i: reg_add_enum_type;
        variable not_found: boolean;
        variable r: reg_val_type;
        variable mask: reg_val_type;
    begin
        if reset_in = '1' then
            regs <= regs_reset_values;
        elsif rising_edge(clk_in) then
            if write_en_in = '1' then
                reg_add_to_index(address_in, i, not_found);
                if not not_found then
                    mask := reg_write_masks(i);
                    r := regs(i) and (not mask);
                    r := r or (writedata_in and mask);
                    regs(i) <= r;
                end if;
            end if;
        end if;
    end process;
   
    -- register read:
    process(reset_in, clk_in)
        variable i: reg_add_enum_type;
        variable not_found: boolean;
        variable r: reg_val_type;
        variable mask: reg_val_type;
        variable m: integer;
    begin
        if reset_in = '1' then
            readdata_out <= (others => '0');
        elsif rising_edge(clk_in) then
            if read_en_in = '1' then
                reg_add_to_index(address_in, i, not_found);
                if not not_found then
                    mask := reg_read_masks(i);
                    r := regs(i);
                    for m in mask'range loop
                        if mask(m) = '0' then
                            r(m) := '-';
                        end if;
                    end loop;
                    readdata_out <= r;
                else
                    readdata_out <= (others => '-');
                end if;
            end if;
        end if;
    end process;
end;

***********************

registers_pkg.vhdl:
***********************
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package registers_pkg is
-- Register definitions here =>
    -- Register value type is here.
    subtype reg_val_type is std_logic_vector(7 downto 0);

    -- List all registers here:
    type reg_add_enum_type is (
        reg_0,
        reg_1,
        reg_2,
        something,
        something_else,
        yet_another
    );
   
    type reg_add_array is array (reg_add_enum_type) of integer;
    -- List register address mapping of every register here:
    constant reg_add_map: reg_add_array := (
        reg_0 => 16#10#,
        reg_1 => 16#11#,
        reg_2 => 16#12#,
        something => 16#20#,
        something_else => 16#21#,
        yet_another => 16#22#
    );
   
    type reg_mask_array is array (reg_add_enum_type) of reg_val_type;
    -- List write_mask of every register here ('0' is Read Only):
    constant reg_write_masks: reg_mask_array := (
        reg_2 => (2 downto 0 => '1', others => '0'),
        something_else => (3 downto 0 => '1', others => '0'),
        others => (others => '1')
    );
    -- List read_mask of every register here ('0' is N.A.):
    constant reg_read_masks: reg_mask_array := (
        reg_0 => (0 => '0', others => '1'),
        others => (others => '1')
    );
   
    type reg_array is array (reg_add_enum_type) of reg_val_type;
    -- List reset_value of every register here:
    constant regs_reset_values: reg_array := (
        others => (others => '0')
    );
-- <= Register definitions here
   
    function reg_array_min(adds: reg_add_array) return integer;
    function reg_array_max(adds: reg_add_array) return integer;
   
    type regs_type is array (reg_add_enum_type) of reg_val_type;
    type reg_mapping_type is array (reg_add_enum_type) of integer;
   
    subtype reg_add_type is integer range reg_array_min(reg_add_map) to reg_array_max(reg_add_map);
    procedure reg_add_to_index(add: in reg_add_type; index: out reg_add_enum_type; error: out boolean);
end;

package body registers_pkg is
    procedure reg_add_to_index(add: in reg_add_type; index: out reg_add_enum_type; error: out boolean) is
        variable i: reg_add_enum_type;
    begin
        for i in reg_add_map'range loop
            if reg_add_map(i) = add then
                error := false;
                index := i;
                return;
            end if;
        end loop;
        assert false report "register address not found!" severity warning;
        index := reg_add_map'right;
        error := true;
    end;
   
    function reg_array_min(adds: reg_add_array) return integer is
        variable f: boolean;
        variable r: integer;
        variable i: reg_add_enum_type;
    begin
        f := false;
        for i in adds'range loop
            if f then
                if adds(i) < r then
                    r := adds(i);
                end if;
            else
                f := true;
                r := adds(i);
            end if;
        end loop;
        assert f report "reg_array_min: adds empty!" severity failure;
        return r;
    end;
   
    function reg_array_max(adds: reg_add_array) return integer is
        variable f: boolean;
        variable r: integer;
        variable i: reg_add_enum_type;
    begin
        f := false;
        for i in adds'range loop
            if f then
                if adds(i) > r then
                    r := adds(i);
                end if;
            else
                f := true;
                r := adds(i);
            end if;
        end loop;
        assert f report "reg_array_max: adds empty!" severity failure;
        return r;
    end;
end;

***********************

1 comment:

  1. I synthesized this on Altera Quartus, Lattice Diamond, and Xilinx WebPack.

    Result was:
    Altera: 56 registers
    Lattice: 47 registers
    Xilinx: 47 registers

    It seems that Quartus, for some reason, does not recognize the constant registers set by reg_write_masks. The correct answer is 47 registers ;)

    -Topi

    ReplyDelete