10.16 An Engine Controller
Chapter start Previous page Next page
10.16 An Engine Controller
This section describes
part of a controller for an automobile engine. Table 10.21 shows a
temperature converter that converts digitized temperature readings from
a sensor from degrees Centigrade to degrees Fahrenheit.
TABLE 10.21 A
temperature converter. |
library IEEE;
use IEEE.STD_LOGIC_1164.all; -- type STD_LOGIC, rising_edge
use IEEE.NUMERIC_STD.all ; -- type UNSIGNED, "+", "/"
entity tconv is generic TPD : TIME:= 1 ns;
port (T_in : in UNSIGNED(11 downto 0);
clk, rst : in STD_LOGIC; T_out : out UNSIGNED(11 downto 0));
end;
architecture rtl of tconv is
signal T : UNSIGNED(7 downto 0);
constant T2 : UNSIGNED(1 downto 0) := "10" ;
constant T4 : UNSIGNED(2 downto 0) := "100" ;
constant T32 : UNSIGNED(5 downto 0) := "100000" ;
begin
process(T) begin T_out <= T + T/T2 + T/T4 + T32 after TPD;
end process;
end rtl;
|
T_in = temperature
in degC
T_out =
temperature in degF
The conversion formula
from Centigrade to Fahrenheit is:
T(degF) = (9/5) x T(degC)
+ 32
This converter uses the
approximation:
9/5 = 1.75 = 1 + 0.5
+ 0.25 |
To save area the temperature
conversion is approximate. Instead of multiplying by 9/5 and adding 32 (so
0 degC becomes 32 degF and 100 degC becomes 212 degF) we multiply by 1.75
and add 32 (so 100 degC becomes 207 degF). Since 1.75 = 1 + 0.5 + 0.25,
we can multiply by 1.75 using shifts (for divide by 2, and divide by 4)
together with a very simple constant addition (since 32 = "100000").
Using shift to multiply and divide by powers of 2 is free in hardware (we
just change connections to a bus). For large temperatures the error approaches
0.05/1.8 or approximately 3 percent. We play these kinds of tricks often
in hardware computation. Notice also that temperatures measured in degC
and degF are defined as unsigned integers of the same width. We could have
defined these as separate types to take advantage of VHDL's type checking.
Table 10.22 describes
a digital filter to compute a "moving average" over four successive
samples in time (i(0), i(1), i(2), and i(3), with i(0) being
the first sample).
TABLE 10.22 A
digital filter. |
library IEEE;
use IEEE.STD_LOGIC_1164.all; -- STD_LOGIC type, rising_edge
use IEEE.NUMERIC_STD.all; -- UNSIGNED type, "+" and "/"
entity filter is
generic TPD : TIME := 1 ns;
port (T_in : in UNSIGNED(11 downto 0);
rst, clk : in STD_LOGIC;
T_out: out UNSIGNED(11 downto 0));
end;
architecture rtl of filter is
type arr is array (0 to 3) of UNSIGNED(11 downto 0);
signal i : arr ;
constant T4 : UNSIGNED(2 downto 0) := "100";
begin
process(rst, clk) begin
if (rst = '1') then
for n in 0 to 3 loop i(n) <= (others =>'0') after TPD;
end loop;
else
if(rising_edge(clk)) then
i(0) <= T_in after TPD;i(1) <= i(0) after TPD;
i(2) <= i(1) after TPD;i(3) <= i(2) after TPD;
end if;
end if;
end process;
process(i) begin
T_out <= ( i(0) + i(1) + i(2) + i(3) )/T4 after TPD;
end process;
end rtl;
|
The filter computes a
moving average over four successive samples in time.
Notice
i(0) i(1) i(2) i(3)
are each 12 bits wide.
Then the sum
i(0) + i(1) + i(2) +
i(3)
is 14 bits wide, and
the
average
( i(0) + i(1) + i(2)
+ i(3) )/T4
is 12 bits wide.
All delays are generic
TPD .
|
The filter uses the following
formula:
T_out <=
( i(0) + i(1) + i(2) + i(3) )/T4
Division by T4 = "100"
is free in hardware. If instead, we performed the divisions before the additions,
this would reduce the number of bits to be added for two of the additions
and saves us worrying about overflow. The drawback to this approach is round-off
errors. We can use the register shown in Table 10.23 to register the
inputs.
TABLE 10.23 The
input register. |
library IEEE;
use IEEE.STD_LOGIC_1164.all; -- type STD_LOGIC, rising_edge
use IEEE.NUMERIC_STD.all ; -- type UNSIGNED
entity register_in is
generic ( TPD : TIME := 1 ns);
port (T_in : in UNSIGNED(11 downto 0);
clk, rst : in STD_LOGIC; T_out : out UNSIGNED(11 downto 0)); end;
architecture rtl of register_in is
begin
process(clk, rst) begin
if (rst = '1') then T_out <= (others => '0') after TPD;
else
if (rising_edge(clk)) then T_out <= T_in after TPD; end if;
end if;
end process;
end rtl ;
|
12-bit-wide register
for the temperature input
signals.
If the input is asynchronous
(from an A/D
converter with a separate
clock, for example), we would need to worry about metastability.
All delays are generic
TPD . |
Table 10.24 shows a first-in,
first-out stack (FIFO). This allows us to buffer the signals coming from
the sensor until the microprocessor has a chance to read them. The depth
of the FIFO will depend on the maximum amount of time that can pass without
the microcontroller being able to read from the bus. We have to determine
this with statistical simulations taking into account other traffic on the
bus.
TABLE 10.24 A
first-in, first-out stack (FIFO). |
library IEEE; use IEEE.NUMERIC_STD.all ; -- UNSIGNED type
use ieee.std_logic_1164.all; -- STD_LOGIC type, rising_edge
entity fifo is
generic (width : INTEGER := 12; depth : INTEGER := 16);
port (clk, rst, push, pop : STD_LOGIC;
Di : in UNSIGNED (width-1 downto 0);
Do : out UNSIGNED (width-1 downto 0);
empty, full : out STD_LOGIC);
end fifo;
architecture rtl of fifo is
subtype ptype is INTEGER range 0 to (depth-1);
signal diff, Ai, Ao : ptype; signal f, e : STD_LOGIC;
type a is array (ptype) of UNSIGNED(width-1 downto 0);
signal mem : a ;
function bump(signal ptr : INTEGER range 0 to (depth-1))
return INTEGER is begin
if (ptr = (depth-1)) then return 0;
else return (ptr + 1);
end if;
end;
begin
process(f,e) begin full <= f ; empty <= e; end process;
process(diff) begin
if (diff = depth -1) then f <= '1'; else f <= '0'; end if;
if (diff = 0) then e <= '1'; else e <= '0'; end if;
end process;
process(clk, Ai, Ao, Di, mem, push, pop, e, f) begin
if(rising_edge(clk)) then
if(push='0')and(pop='1')and(e = '0') then Do <= mem(Ao); end if;
if(push='1')and(pop='0')and(f = '0') then mem(Ai) <= Di; end if;
end if ;
end process;
process(rst, clk) begin
if(rst = '1') then Ai <= 0; Ao <= 0; diff <= 0;
else if(rising_edge(clk)) then
if (push = '1') and (f = '0') and (pop = '0') then
Ai <= bump(Ai); diff <= diff + 1;
elsif (pop = '1') and (e = '0') and (push = '0') then
Ao <= bump(Ao); diff <= diff - 1;
end if;
end if;
end if;
end process;
end;
|
FIFO (first-in, first-out)
register
Reads (pop = 1) and writes
(push = 1) are synchronous to the rising edge of the clock.
Read and write should
not occur at the same time. The width (number of bits in each word) and
depth (number of words) are generics.
External signals:
clk , clock
rst , reset
active-high
push , write
to FIFO
pop , read
from FIFO
Di , data
in
Do , data
out
empty ,
FIFO flag
full , FIFO
flag
Internal signals:
diff , difference
pointer
Ai , input
address
Ao , output
address
f , full
flag
e , empty
flag
No delays in this model. |
The FIFO has flags, empty
and full , that signify its state. It uses a function to increment
two circular pointers. One pointer keeps track of the address to write to
next, the other pointer tracks the address to read from. The FIFO memory
may be implemented in a number of ways in hardware. We shall assume for
the moment that it will be synthesized as a bank of flip-flops.
Table 10.25 shows a controller
for the two FIFOs. The controller handles the reading and writing to the
FIFO. The microcontroller attached to the bus signals which of the FIFOs
it wishes to read from. The controller then places the appropriate data
on the bus. The microcontroller can also ask for the FIFO flags to be placed
in the low-order bits of the bus on a read cycle. If none of these actions
are requested by the microcontroller, the FIFO controller three-states its
output drivers.
Table 10.25 shows the
top level of the controller. To complete our model we shall use a package
for the component declarations:
TABLE 10.25 A
FIFO controller. |
library IEEE;use IEEE.STD_LOGIC_1164.all;use IEEE.NUMERIC_STD.all;
entity fifo_control is generic TPD : TIME := 1 ns;
port(D_1, D_2 : in UNSIGNED(11 downto 0);
sel : in UNSIGNED(1 downto 0) ;
read , f1, f2, e1, e2 : in STD_LOGIC;
r1, r2, w12 : out STD_LOGIC; D : out UNSIGNED(11 downto 0)) ;
end;
architecture rtl of fifo_control is
begin process
(read, sel, D_1, D_2, f1, f2, e1, e2)
begin
r1 <= '0' after TPD; r2 <= '0' after TPD;
if (read = '1') then
w12 <= '0' after TPD;
case sel is
when "01" => D <= D_1 after TPD; r1 <= '1' after TPD;
when "10" => D <= D_2 after TPD; r2 <= '1' after TPD;
when "00" => D(3) <= f1 after TPD; D(2) <= f2 after TPD;
D(1) <= e1 after TPD; D(0) <= e2 after TPD;
when others => D <= "ZZZZZZZZZZZZ" after TPD;
end case;
elsif (read = '0') then
D <= "ZZZZZZZZZZZZ" after TPD; w12 <= '1' after TPD;
else D <= "ZZZZZZZZZZZZ" after TPD;
end if;
end process;
end rtl;
|
This handles the reading
and writing to the FIFOs under control of the processor (mpu). The mpu can
ask for data from either FIFO or for status flags to be placed on the bus.
Inputs:
D_1
data
in from FIFO1
D_2
data
in from FIFO2
sel
FIFO
select from mpu
read
FIFO
read from mpu
f1,f2,e1,e2
flags
from FIFOs
Outputs:
r1, r2
read
enables for FIFOs
w12
write
enable for FIFOs
D
data
out to mpu bus |
TABLE 10.26 Top
level of temperature controller. |
library IEEE; use IEEE.STD_LOGIC_1164.all; use IEEE.NUMERIC_STD.all;
entity T_Control is port (T_in1, T_in2 : in UNSIGNED (11 downto 0);
sensor: in UNSIGNED(1 downto 0);
clk, RD, rst : in STD_LOGIC; D : out UNSIGNED(11 downto 0));
end;
architecture structure of T_Control is use work.TC_Components.all;
signal F, E : UNSIGNED (2 downto 1);
signal T_out1, T_out2, R_out1, R_out2, F1, F2, FIFO1, FIFO2 : UNSIGNED(11 downto 0);
signal RD1, RD2, WR: STD_LOGIC ;
begin
RG1 : register_in generic map (1ns) port map (T_in1,clk,rst,R_out1);
RG2 : register_in generic map (1ns) port map (T_in2,clk,rst,R_out2);
TC1 : tconv generic map (1ns) port map (R_out1, T_out1);
TC2 : tconv generic map (1ns) port map (R_out2, T_out2);
TF1 : filter generic map (1ns) port map (T_out1, rst, clk, F1);
TF2 : filter generic map (1ns) port map (T_out2, rst, clk, F2);
FI1 : fifo generic map (12,16) port map (clk, rst, WR, RD1, F1, FIFO1, E(1), F(1));
FI2 : fifo generic map (12,16) port map (clk, rst, WR, RD2, F2, FIFO2, E(2), F(2));
FC1 : fifo_control port map
(FIFO1, FIFO2, sensor, RD, F(1), F(2), E(1), E(2), RD1, RD2, WR, D);
end structure;
|
package TC_Components is
component register_in generic (TPD : TIME := 1 ns);
port (T_in : in UNSIGNED(11 downto 0);
clk, rst : in STD_LOGIC; T_out : out UNSIGNED(11 downto 0));
end component;
component tconv generic (TPD : TIME := 1 ns);
port (T_in : in UNSIGNED (7 downto 0);
clk, rst : in STD_LOGIC; T_out : out UNSIGNED(7 downto 0));
end component;
component filter generic (TPD : TIME := 1 ns);
port (T_in : in UNSIGNED (7 downto 0);
rst, clk : in STD_LOGIC; T_out : out UNSIGNED(7 downto 0));
end component;
component fifo generic (width:INTEGER := 12; depth : INTEGER := 16);
port (clk, rst, push, pop : STD_LOGIC;
Di : UNSIGNED (width-1 downto 0);
Do : out UNSIGNED (width-1 downto 0);
empty, full : out STD_LOGIC);
end component;
component fifo_control generic (TPD:TIME := 1 ns);
port (D_1, D_2 : in UNSIGNED(7 downto 0);
select : in UNSIGNED(1 downto 0); read, f1, f2, e1, e2 : in STD_LOGIC;
r1, r2, w12 : out STD_LOGIC; D : out UNSIGNED(7 downto 0)) ;
end component;
end;
The following testbench completes
a set of reads and writes to the FIFOs:
library IEEE;
use IEEE.std_logic_1164.all; -- type STD_LOGIC
use IEEE.numeric_std.all; -- type UNSIGNED
entity test_TC is end;
architecture testbench of test_TC is
component T_Control port (T_1, T_2 : in UNSIGNED(11 downto 0);
clk : in STD_LOGIC; sensor: in UNSIGNED( 1 downto 0) ;
read : in STD_LOGIC; rst : in STD_LOGIC;
D : out UNSIGNED(7 downto 0)); end component;
signal T_1, T_2 : UNSIGNED(11 downto 0);
signal clk, read, rst : STD_LOGIC;
signal sensor : UNSIGNED(1 downto 0);
signal D : UNSIGNED(7 downto 0);
begin TT1 : T_Control port map (T_1, T_2, clk, sensor, read, rst, D);
process begin
rst <= '0'; clk <= '0';
wait for 5 ns; rst <= '1'; wait for 5 ns; rst <= '0';
T_in1 <= "000000000011"; T_in2 <= "000000000111"; read <= '0';
for i in 0 to 15 loop -- fill the FIFOs
clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns;
end loop;
assert (false) report "FIFOs full" severity NOTE;
clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns;
read <= '1'; sensor <= "01";
for i in 0 to 15 loop -- empty the FIFOs
clk <= '0'; wait for 5ns; clk <= '1'; wait for 5 ns;
end loop;
assert (false) report "FIFOs empty" severity NOTE;
clk <= '0'; wait for 5ns; clk <= '1'; wait;
end process;
end;
|