2.2 Structdes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 28

A Structured VHDL Design Method

Jiri Gaisler

CTH / Gaisler Research


Outline of lecture

Traditional 'ad-hoc' VHDL design style


Proposed structured design method
Various ways of increasing abstraction level in
synthesisable code
A few design examples
Traditional design methods

Many concurrent statments


Many signal
Few and small process statements
No unified signal naming convention
Coding is done at low RTL level:
Assignments with logical expressions
Only simple array data structures are used
Problems

Slow execution due to many signals and processes


Dataflow coding difficult to understand
Algorithm difficult to understand
No distinction between sequential and comb. signals
Difficult to identify related signals
Large port declarations in entity headers
Modelling requirements

We want our models to be:


Easy to understand and maintain
Simulate as fast as possible
Synthesisable
No simulation/synthesis discrepancies
Abstraction of digital logic
A synchronous design can be abstracted into two
separate parts; a combinational and a sequential

q
Comb
d q = f(d,qr)
DFF

qr

Clk
Implementing the abstracted view in VHDL:
The two-process scheme
A VHDL entity is made to contain only two processes: one
sequential and one combinational
Two local signals are declared:
register-in (r i n ) and register-out (r )
The full algorithm (q = f(d,r ))is performed in the
combinational process
The combinational process is sensitive to all input ports and
the register outputs r
The sequential process is only sensitive to the clock
Two-process VHDL entity

qc Out-port
In-ports Comb.
d Process
qcn = f(d,r) r in Seq.
Process

r
Clk
Two-process scheme: data types

The local signals r and r i n are of composite type (record) and


include all registered values
All outputs are grouped into one entity-specific record type,
declared in a global interface package
Input ports are of output record types from other entities
A local variable of the registered type is declared in the
combinational processes to hold newly calculated values
Additional variables of any type can be declared in the
combinational process to hold temporary values
Example
use work.interface.all; begin

entity irqctrl is port ( comb : process (sysif, r)


clk : in std_logic; variable v : reg_type;
rst : in std_logic; begin
sysif : in sysif_type; v := r; v.irq := '0';
irqo : out irqctrl_type); for i in r.pend'range loop
end; v.pend := r.pend(i) or
(sysif.irq(i) and r.mask(i));
architecture rtl of irqctrl is v.irq := v.irq or r.pend(i);
end loop;
type reg_type is record rin <= v;
irq : std_logic; irqo.irq <= r.irq;
pend : std_logic_vector(0 to 7); end process;
mask : std_logic_vector(0 to 7);
end record;
reg : process (clk)
signal r, rin : reg_type; begin
if rising_edge(clk) then
r <= rin;
end if;
end process;

end architecture;
Hierarchical design
use work.interface.all;
Grouping of signals makes
entity cpu is port (
code readable and shows the clk : in std_logic;
direction of the dataflow rst : in std_logic;
mem_in : in mem_in_type;
mem_out : out mem_out_type);
end;
Clk, rst
Proc architecture rtl of cpu is
signal cache_out : cache_type;
signal proc_out : proc_type;
signal mctrl_out : mctrl_type;
begin
Cache
u0 : proc port map
(clk, rst, cache_out, proc_out);

u1 : cache port map


Mctrl (clk, rst, proc_out, mem_out cache_out);

u2 : mctrl port map


(clk, rst, cache_out, mem_in, mctrl_out,
mem_out);

end architecture;
Memory
Benefits

Sequential coding is well known and understood


Algorithm easily extracted
Uniform coding style simplifies maintenance
Improved simulation and synthesis speed
Development of models is less error-prone
Adding an port

Traditional method: Two-process method:


Add port in entity port Add element in the interface
declaration record
Add port in sensitivity list of
appropriate processes (input
ports only)
Add port in component
declaration
Add signal declaration in parent
module(s)
Add port map in component
instantiation in parent module(s)
Adding a register

Traditional method: Two-process method:


Add signal declaration (2 sig.) Add definition in register record
Add registered signal in process
sensitivity list (if not implicite)
(Declare local variable)
Add driving statement in clocked
process
Tracing signals during debugging

Traditional method: Two-process method:


Figure out which signals are Add interface records, r and r i n
registered, which are their inputs,
and how they are functionally Signals are grouped according to
related function and easy to understand

Add signals to trace file Addition/deletion of record


elements automatically
Repeat every time a port or propagated to trace window
register is added/deleted
Stepping through code during debugging

Traditional method: Two-process method:


Connected processes do not Add a breakpoint in the begining
execute sequentially due to delta of the combinational process
signal delay
Single-step through code to
A breakpoint in every connected execute complete algorithm
process needed
Next signal value (r i n ) directly
New signal value in concurrent visible in variable v
processes not visible
Complete algorithm can be a sub-program

Allows re-use if placed in a comb : process (sysif, r, rst)


variable v : reg_type;
global package (e.g. EDAC) begin

Can be verified quickly with proc_irqctl(sysif, r, v);


local test-bench rin <= v;
irqo.irq <= r.irq;
Meiko FPU (20 Kgates): end process;

1 entity, 2 processes
44 sub-programs
13 signal assignments
Reverse-engineered from
verilog: 87 entities, ~800
processes, ~2500 signals
Sequential code and synthesis

Most sequential statements comb : process (sysif, r, rst)


variable v : reg_type;
directly synthesisable by modern begin
tools
proc_irqctl(sysif, r, v);
All variables have to be assigned if rst = '1' then
to avoid latches v.irq := '0';
v.pend := (others => '0');
Order of code matters! end if;

rin <= v;
Avoid recursion, division, access irqo.irq <= r.irq;
types, text/file IO. end process;
Comparison MEC/LEON

ERC32 memory contoller MEC LEON SPARC-V8 processor


Ad-hoc method (15 designers) Two-process method (mostly)
25,000 lines of code 15,000 lines of code
45 entities, 800 processes 37 entities, 75 processes
2000 signals 300 signals
3000 signal assigments 800 signal assigments
30 Kgates, 10 man-years, 100 Kgates, 2 man-years, no
numerous of bugs, 3 iterations bugs in first silicon
Increasing the abstraction level

Benefits Problems
Easier to understand the Keep the code synthesisable
underlying algorithm
Synthesis tool might choose
Easier to modify/maintain wrong gate-level structure
Faster simulation Problems to understand
algorithm for less skilled
Use built-in module engineers
generators (synthesis)
Using records

Useful to group related signals type reg1_type is record


f1 : std_logic_vector(0 to 7);
f2 : std_logic_vector(0 to 7);
Nested records further improves f3 : std_logic_vector(0 to 7);
readability end record;

Directly synthesisable type reg2_type is record


x1 : std_logic_vector(0 to 3);
x2 : std_logic_vector(0 to 3);
Element name might be difficult x3 : std_logic_vector(0 to 3);
to find in synthesised netlist end record;

type reg_type is record


reg1 : reg1_type;
reg2 : reg2_type;
end record;

variable v : regtype;

v.reg1.f3 := “0011001100”;
Using ieee.std_logic_arith.all;

Written by Synopsys, now freely type unsigned is array (natural range


<>) of st_logic;
available type signed is array (natural range
<>) of st_logic;
Declares to additional types:
signed and unsigned variable u1, u2, u3 : unsigned;
variable v1 : std_logic_vector;
Declares arithmetic and various
conversion operators: +, -, *, /, <,
u1 := u1 + (u2 * u3);
>, =, <=, >=, /=, conv_integer
if (v1 >= v2) then ...
Built-in, optimised versions
v1(0) := u1(conv_integer(u2));
available in all simulators and
synthesis tools
IEEE alternative: numeric_std
Use of loops

Used for iterative calculations variable v1 : std_logic_vector(0 to 7);


variable first_bit : natural;
Index variable implicitly -- find first bit set
declared for i in v1'range loop
if v1(i) = '1' then
Typical use: iterative algorithms, first_bit := i; exit;
end if;
priority encoding, sub-bus end loop;
extraction, bus turning
-- reverse bus
for in 0 to 7 loop
v1(i) := v2(7-i);
end loop;
Multiplexing using integer conversion

N to 1 multiplexing function genmux(s, v : std_logic_vector)


return std_logic is
variable res : std_logic_vector(v'length-1
downto 0);
variable i : integer;
begin
res := v; -- needed to get correct index
i := conv_integer(unsigned(s));
return(res(i));
end;

function decode(v : std_logic_vector) return


N to 2**N decoding std_logic_vector is
variable res :
std_logic_vector((2**v'length)-1 downto 0);
variable i : natural;
begin
res := (others => '0');
i := conv_integer(unsigned(v));
res(i) := '1';
return(res);
end;
State machines
architecture rtl of mymodule is
Simple case-statement type state_type is (first, second, last);
type reg_type is record
implementation state : state_type;
drive : std_logic;
Maintains current state end record;
signal r, rin : reg_type;
Both combinational and begin
comb : process(...., r)
registered output possible begin
case r.state is
when first =>
if cond0 then v.state := second; end if;
when second =>
if cond1 then v.state := first;
elsif cond2 then v.state := last; end if;
when others =>
v.drive := '1'; v.state := first;
end case;
if reset = '1' then v.state := first; end if;
modout.cdrive <= v.drive; -- combinational
modout.rdrive <= r.drive; -- registered
end process;
.
Conclusions

The two-process design method provides a uniform


structure, and a natural division between algorithm
and state
It improves
Development time (coding, debug)
Simulation and synthesis speed
Readability
Maintenance and re-use

You might also like