Tuesday, June 21, 2011

Digital calipers - open the small cap - and a new way of implementing behavioral programming

Hi,

Have you ever noticed those cheap Chinese digital calipers, like these, have a 4-pin PWB connector under a small cap.

Yup, the measured length is outputted from that connector. And now you can read the value into your FPGA desing:

download from here

The thing I want you to pay attention is not the reader block (that is extra bonus), but the model itself.

In the process starting fom line 192, I used a totally new (for me) approach to avoid writing yet another boring state machine.

In the main loop, I use a variable "t" of type time, to compose a waveform for clk_int and data_int nets.

The magical keyword used is "transport", which allows you to assign a new future events onto a signal, without deleting previous assignments.

So the whole sequence (of outputting 48 bits of data and clock) is updated simultaneously (from the simulation time point of view). Here's a snippet (line 219):
  for i in 1 to 23 loop
    clk_int <= transport '0' after t;
    t := t + clock_time;
    clk_int <= transport '1' after t;
    if w2(i) = '0' then
      data_int <= transport '1' after t+output_delay;
      data_int <= transport '0' after t+output_delay+glitch_time;
    else
      data_int <= transport w2(i) after t+output_delay;
    end if;
    t := t + clock_time;
  end loop;


The variable t is updated every now and then, and all signal assignments are done with "after" suffix and utilizing "transport" model.

On line 294, two timers are launched and the wait sentence will decide when to continue:
  trig_slow <= 0, 1 after rate_slow;
  trig_fast <= 0, 1 after rate_fast;
  wait until reset_in='1' or
   (mode=normal and trig_slow=1) or (mode/=normal and trig_fast=1);
  exit when reset_in = '1';

If the reset_in is hitted by '1' during the output sequence, the model will exit the loop immediately and overwrites any waveform left on data_int and clk_int, on line 202/203:
  data_int <= 'L';
  clk_int <= 'L';

I haven't yet tried this on a real caliper and FPGA. If I happen to have some spare time during summer holiday, I'll give it a try and tell the results here.

BR, -Topi

registers - getting even better

I failed to use previous version of register builder and needed to change the design pattern a bit.

vhdl registers ver_1

Has utilized generate -syntax to decide which registers to write on each situation.

You don't need to touch on registers.vhdl to expand the design. registers_pkg.vhdl describes all the functionality of whole register set, and (in the example provided) tester_registers.vhdl decides what features to actually use. The synthesizer (Altera failed again, by generating extra registers for this example) will optimize out any unwanted feature.

In the registers_pkg.vhdl lines...

    type reg_add_enum_type is (
        add_0,
        read_only,
        write_only,
        read_write,
        lo_nibble_rw,
        realtime
    );


    constant reg_add_map: reg_add_array := (
        add_0 => 0,
        read_only => 1,
        write_only => 2,
        read_write => 3,
        lo_nibble_rw => 4,
        realtime => 5
    );


    constant reg_mask: reg_mask_array := (
        add_0 => "********",
        read_only => "zzzzRRRR",
        write_only => "WWWWWWWW",
        read_write => "********",
        lo_nibble_rw => "r---****",
        realtime => "rrrrrrrr"
    );



...describe the behaviour of register set. The first, type definition, lists user-friendly names of all registers. The number of registers is the number of entries in the type.


Second one is an array listing physical addresses (from the CPU point of view) of each register. They don't have to be successive, and can have holes in the address map.

Third one tells the wanted behaviour of each register bit:
'*' means the bit can be written and read by CPU. And the FPGA has read/write access too.
'z' means the bit can be read by CPU, and reading will reset the bit to zero. FPGA has read/write access to the bit.
'R' means the bit is read only for the CPU. FPGA has read/write acces.
'W' means the bit is write only from the CPU point of view. FPGA has read/write access.
'r' means the bit is readable by CPU, and the data is passed through the register block without any storage (D-FlipFlop) (realtime access).
'-' means the bit is not used. CPU read will always return zero, and it cannot be written to.

On the block instantiating (tester_registers.vhdl) the register:

FPGA writes to non-realtime register [*zRW] you need two lines of code (lines 35/36):
    reg_update_en(read_only) <= write_ro_in;
    reg_updates(read_only) <= data_ro_in;
In this case the register named "read_only" is updated whenever write_ro_in is '1' for 1 clock cycle. The data written is in data_ro_in.

Feeding in the realtime data (line 31):
    regs_realtime(realtime) <= realtime_in;
In this case the register named "realtime" is constantly updated with signal realtime_in.

And finally getting register values out from the block (line 33):
    test_reg_out <= regs(realtime) & regs(read_write);
Notice that whole register set (all registers) are routed out from the register block. But synthesizer will optimize out any registers that are not used. In this case "regs" holds 48 bits, only 16 of them will be synthesized. You can even feed the same signal (regs) to all your blocks needing (read) access to register values, and your block can fetch out specific values for it.

And, in this case the registers were 8-bit, but I'm sure you will find out a way to change that ;)

BR, -Topi

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;

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