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;

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

Saturday, May 28, 2011

Hi,

In this blog, I will publish VHDL models of different kind and usage.

I'll start with a simple block, that I hope to need in near future.

MCP3201 is 12-bit A/D converter, with semi-differential input and 100 ksps rate.

So here we go, more details in following posts...

$ ghdl --pp-html ic_mcp3201.vhdl >ic_mcp3201.vhdl.html


1 library ieee;
2 use ieee.std_logic_1164.all;
3 use ieee.numeric_std.all;
4 use ieee.math_real.all;
5
6 entity ic_mcp3201 is
7 port(
8 in_p_in: in real;
9 in_n_in: in real;
10 vref_in: in real;
11 vdd_in: in real := 5.0;
12 clk_in: in std_logic;
13 cs_in: in std_logic;
14 dout_out: out std_logic
15 );
16 end;
17
18 architecture behavioral of ic_mcp3201 is
19 constant tsucs_min: time := 100 ns;
20 constant ten_min: time := 0 ns;
21 constant ten_max: time := 200 ns;
22 constant tdo_min: time := 0 ns;
23 constant tdo_max: time := 200 ns;
24 constant thi_min: time := 312 ns;
25 constant tlo_min: time := 312 ns;
26 constant tr_min: time := 0 ns;
27 constant tr_max: time := 100 ns;
28 constant tf_min: time := 0 ns;
29 constant tf_max: time := 100 ns;
30 constant tdis_min: time := 0 ns;
31 constant tdis_max: time := 100 ns;
32 constant tcsh_min: time := 625 ns;
33
34 signal cs: std_logic;
35 signal clk: std_logic;
36
37 signal sh_active: std_logic;
38 signal v_c_sample: real := 0.0; -- Sample capacitor value.
39 signal ad_data: unsigned(11 downto 0);
40 signal dout_1: std_logic;
41 begin
42 cs <= to_x01(cs_in);
43 clk <= to_x01(clk_in);
44
45 SH_in: process(in_p_in, in_n_in, v_c_sample, sh_active)
46 constant r_res: real := 1.0e3;
47 constant c_cap: real := 20.0e-12;
48 constant resolution: real := 1.0/(2.0**12); -- 1.0 LSB.
49 variable v_res: real;
50 variable i_res: real;
51 variable delay: real;
52 variable t_delay: time;
53 variable v_in: real;
54 variable v_next: real;
55 variable res: real;
56 variable v_prev: real;
57 variable tolog: real;
58 begin
59 v_in := in_p_in - in_n_in;
60 v_prev := realmax(0.0,v_c_sample);
61 v_prev := realmin(vref_in,v_prev);
62 if false then
63 if sh_active = '1' then
64 v_c_sample <= v_in;
65 end if;
66 elsif sh_active = '1' then
67 res := vref_in*resolution;
68 tolog := (v_in-v_prev)/(v_in-v_prev-res);
69 if tolog>1.0+1.0e-12 then
70 delay := r_res*c_cap*log(tolog);
71 else
72 delay := 1.0;
73 end if;
74 delay := realmax(1.0e-12, delay);
75 delay := realmin(1.0, delay);
76 t_delay := delay * 1 sec;
77 v_next := v_prev + (v_in-v_prev)*(1.0-exp(-delay/(r_res*c_cap)));
78 v_next := realmax(0.0, v_next);
79 v_next := realmin(vref_in, v_next);
80 v_c_sample <= v_next after t_delay;
81 end if;
82 end process;
83
84 process(v_c_sample, vref_in)
85 variable r: real;
86 variable i: integer;
87 begin
88 r := v_c_sample/vref_in*4096.0;
89 r := realmin(r, 4095.0);
90 r := realmax(r, 0.0);
91 i := integer(round(r));
92 ad_data <= to_unsigned(i,12);
93 end process;
94
95 logic: process
96 variable i: integer;
97 begin
98 dout_1 <= 'Z';
99 sh_active <= '0';
100 wait until falling_edge(cs_in);
101 wait until rising_edge(clk_in) or to_x01(cs_in) /= '0';
102 if to_x01(cs_in) = '0' then
103 sh_active <= '1';
104 wait until rising_edge(clk_in) or to_x01(cs_in) /= '0';
105 if to_x01(cs_in) = '0' then
106 wait until falling_edge(clk_in) or to_x01(cs_in) /= '0';
107 sh_active <= '0';
108 if to_x01(cs_in) = '0' then
109 dout_1 <= 'X' after ten_min, '0' after ten_max;
110 for i in 11 downto 0 loop
111 wait until falling_edge(clk_in) or to_x01(cs_in) /= '0';
112 exit when to_x01(cs_in) /= '0';
113 dout_1 <= 'X' after tdo_min, ad_data(i) after tdo_max;
114 end loop;
115 for i in 1 to 11 loop
116 exit when to_x01(cs_in) /= '0';
117 wait until falling_edge(clk_in) or to_x01(cs_in) /= '0';
118 exit when to_x01(cs_in) /= '0';
119 dout_1 <= 'X' after tdo_min, ad_data(i) after tdo_max;
120 end loop;
121 loop
122 exit when to_x01(cs_in) /= '0';
123 wait until falling_edge(clk_in) or to_x01(cs_in) /= '0';
124 exit when to_x01(cs_in) /= '0';
125 dout_1 <= 'X' after tdo_min, '0' after tdo_max;
126 end loop;
127 end if;
128 end if;
129 end if;
130 end process;
131
132 dout_driver: process(dout_1, cs_in)
133 begin
134 if to_x01(cs_in) = 'X' then
135 dout_out <= 'X';
136 elsif to_x01(cs_in) = '1' then
137 dout_out <= 'X' after tdis_min, 'Z' after tdis_max;
138 else
139 dout_out <= dout_1;
140 end if;
141 end process;
142
143 check_tlo_thi: process(cs, clk)
144 variable prev_event: time := 0 ns;
145 begin
146 if cs /= '1' then
147 if rising_edge(clk) then
148 assert now-prev_event >= tlo_min report "TLO failure" severity error;
149 elsif falling_edge(clk) then
150 assert now-prev_event >= thi_min report "TLO failure" severity error;
151 end if;
152 end if;
153 if clk'event then
154 prev_event := now;
155 end if;
156 end process;
157
158 check_tsucs: process(cs, clk)
159 variable cs_fall_t: time := 0 ns;
160 variable clk_rise_t: time := 0 ns;
161 begin
162 if falling_edge(cs) then
163 cs_fall_t := now;
164 end if;
165 if cs /= '1' and rising_edge(clk) then
166 clk_rise_t := now;
167 assert clk_rise_t - cs_fall_t >= tsucs_min report "TSUCS failure" severity failure;
168 end if;
169 end process;
170
171 check_tcsh: process(cs)
172 variable prev_event: time := 0 ns;
173 begin
174 if falling_edge(cs) then
175 assert now-prev_event >= tcsh_min report "TCSH failure" severity failure;
176 end if;
177 if cs'event then
178 prev_event := now;
179 end if;
180 end process;
181 end;
182
183 library ieee;
184 use ieee.std_logic_1164.all;
185 use ieee.numeric_std.all;
186
187
188 entity tb_ic_mcp3201 is
189 end;
190
191 architecture tb of tb_ic_mcp3201 is
192 signal in_p: real;
193 signal in_n: real;
194 signal vref: real := 5.0;
195 signal vdd: real := 5.0;
196 signal clk: std_logic;
197 signal cs: std_logic;
198 signal dout: std_logic;
199
200 begin
201 clk_driver: process
202 begin
203 clk <= '0';
204 wait for 0.35 us;
205 clk <= '1';
206 wait for 0.4 us;
207 end process;
208
209 process
210 begin
211 in_n <= 0.0;
212 in_p <= 0.0;
213 vref <= 5.0;
214 vdd <= 5.0;
215 cs <= '1';
216 wait for 1 us;
217 cs <= '0';
218 wait for 1 us;
219 cs <= '1';
220 wait for 625 ns;
221 cs <= '0';
222 wait for 10 ns;
223 cs <= '1';
224 loop
225 wait for 10 us;
226 wait until falling_edge(clk);
227 cs <= '0';
228 wait for 40 us;
229 cs <= '1';
230 in_p <= in_p + 0.03;
231 end loop;
232 end process;
233
234 DUT: entity work.ic_mcp3201
235 port map(
236 in_p_in => in_p,
237 in_n_in => in_n,
238 vref_in => vref,
239 vdd_in => vdd,
240 clk_in => clk,
241 cs_in => cs,
242 dout_out => dout
243 );
244 end;
245