vhdl
- Details
- Last Updated: Wednesday, 01 May 2019 13:45
- Published: Wednesday, 01 May 2019 13:45
- Hits: 931
VHDL: VHSIC (Very high speed IC) hardware description language. IEEE (std 1076) standardized the language in 1987 (called as VHD-1987). It was again updated in 1993 called as VHDL 1076-1993 which is the most widely used. Later VHDL-2000, VHDL-2002 and VHDL-2008 were released with minor improvements.
VHDL-1993: allowed a component to be directly instantiated by using entity name and also allowed use of shared variables. Also allowed string to be rep in binary/octal/hex.
VHDL-2000: protected types were added so that shared variables from VHDL-1993 can be used in a useful way.
VHDL-2002: rules on using buffer ports were relaxed.
VHDL-2008: enhanced significantly compared to previous versions. It allowed use of process(all) so that signals don't need to be put in sensitivity list (similar to always @* in verilog)
------
VHDL is CASE INSENSITIVE. So pin "EN" is same as pin "en", and can be considered as connected to each other. However, we use capital letters for reserved keywords for better readability.
VHDL is free-form in the sense that blank lines, spaces, etc may be included for readability w/o any ill effects.
VHDL Library:
--------------
VHDL requires all design sources to be in a library, VHDL also allows named libraries that can contain one or more files. VHDL design units can access other design units in the same and different libraries by declaring the name of the library and design unit to make visible. In VHDL, we've 3 kind of lib:
1. Project libraries: These are the libraries in which you store your designs. You have full read-write access to these libraries. These are our Source dir files. Usually compiled in WORK lib. i.e to access "module1", we reference it as "work.module1".
2. External libraries: These are libraries that you need in your design, but that can be treated as read-only. These are libraries from other designs or from somewhere else.
3. Built-in libraries: The standard VHDL libraries, STD and IEEE, are built-in built in lib. This means that they are always available without any additional configuration. This is done for convenience, as any VHDL project will need parts of them. For ex cadence simulators will have predefined vhdl package for vhdl std lib, std logic, etc.
- Lib STANDARD: provides behavioral data types and operators: types=character, string, bit, boolean, integer, real, time
- Lib IEEE: provides synthesis and simulation data types and operators:. Various pacakages:
- std_logic_1164: It's IEEE pkg. inluded by default. added types= std_logic, std_logic_vector (also added std_ulogic and std_ulogic_vector).
- std_logic_arith: It's synopsys pkg but included in IEEE. It defines arithmetic and comparison operators for std_logic_vector. It added new types "unsigned" and "signed" which are array of std_logic (similar to std_logic_vector but arithmetic operators can be used on these)
- std_logic_signed/std_logic_unsigned: It's synopsys pkg but included in IEEE. It defines arithmetic and comparison operators for std_logic_vector. signed implies std_logic_vector is treated as signed number, while unsigned implies it's treated as unsigned. Don't use std_logic_signed.
- numeric_std: It's IEEE pkg intended to replace above 2 synopsys pkg. It added new types "unsigned" and "signed" which are array of std_logic, and defines arith, comp and logic operatrs on these 2 types. Recommended to use this instead of 2 synopsys pkg above. However, it didn't allow arithmetic to be done directly on std_logic_vector. For this, VHDL-2008 added Numeric_Std_Unsigned and Numeric_Std_Signed, which can be used similar to std_logic_signed/std_logic_unsigned of synopsys pkg.
library STD; -- built in STD lib. no need to declare STD library as it's included by default
use STD.standard.all;
use STD.textio.all;
library IEEE; -- built in IEEE lib. only std_logic_1164 is included by default. Others have to be declared in order to be included.
use IEEE.std_logic_1164.all; -- defines std_logic and std_logic_vector
--use IEEE.std_logic_arith.all; -- defines arithmetic operations on std_logic_vector
--use IEEE.std_logic_unsigned.all; -- defines std_logic_vector to be unsigned (for signed, use IEEE.std_logic_signed.all)
use IEEE.numeric_std.all; -- instead of std_logic_arith and std_logic_signed/unsigned, use numeric_std
library TIDLIB; -- External libraries that contain common primitive defn, other common blocks, etc. We compile the files containing packages using option "-work TIDLIB". So, all compiles packages go into TIDLIB library.
use TIDLIB.TID_COMMON_PKG.all; => contains package TID_COMMON_PKG (defining various constants and common component as CLK_SOURCE, CLK_DIV, CLK_GATER, etc) in TID_COMMON_PKG,vhd
library work; -- project library WORK
use work.REGADDR_pkg.all; -- we use this as some pkg may have been compiled separately, but put in libraray work.
COMPILE: When we compile using irun or ncvhdl, we can provide option for work dir using "-work". For ex: "ncvhdl -work mylib" will put all compiled design in mylib design library as mylib/inca.lnx86.010.pak. Usually *INCA_libs/worklib/* has compiled inca.lnx86*.pak binary files in separate dir for std(inca.std), ieee(inca.ieee), synopsys(inca.synopsys) and work(inca.work) library.
--------------------------------
cmds in vhdl: vhdl files are parsed for objects(identifiers), their types, operators and various reserved keywords.
------------------
#types: VHDL is strongly typed lang. Explicit conversion is usually required: ex: BIT'('1') => converts character '1' to bit '1'. (NOTE: '1' may have already been a bit, but converting it makes sure, it's become a bit). Lowest value or left most value is the default value for that type. 1 found in vhdl file is assigned type integer, -1.0 is assigned type real, 'a' is assigned type character, while a is considered an object identifier and has to be assigned one of the types shown below.
1. std types in STD pkg: (PACKAGE STANDARD is .... END STANDARD;). For cadence, this exists in /apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/std/standard.vhdl
I. scalar
A. boolean: type boolean is (false,true); (or TRUE, FALSE, True, False). default is FALSE. Relational operators (=, <=, >=, /=) produce boolean result, which is tested in IF stmt.
B. bit: type bit is ('0','1'); (NOTE: different than integers 0,1. Bit '0','1' are just 2 character lierals '0','1', but explicit conversion may be required from bit to character or vice versa). To covert boolean type to bit, explicit conversion is required: ex: BIT'(FALSE). default is '0' as expected.
C. integer: type integer is (-2,147,483,647 to +2,147,483,647) => no commas, shown only for clarity. interger can have subtypes defined.
1. subtype NATURAL is INTEGER range 0 to INTEGER'HIGH;
2. subtype POSITIVE is INTEGER range 1 to INTEGER'HIGH;
D. charcter: single character enclosed in single quotes. type character is (NUL, SOH, etc .., 'a-z', 'A-Z','0-9', ' ',''','$','@','%'). ex: '@'. default is NUL (NOTE: NUL, etc are not enclosed in single quotes). character literal '0','1' are same as bit literal '0','1', though explicit conversion may be required. 'a' is different than 'A' (even though vhdl is NOT case sensitive).
E. real: type real is (-1.7014110E +38 to +1.7014110E +38). ex: 1.2
F. text: used for file operations. see below under files.
G. time: type TIME is range -9223372036854775808 to 9223372036854775807 units fs; ps = 1000fs; ... hr=60min; end units; units are fs,ps,ns,us,ms,sec,min,hr. ex: 2.1 ns (integer/real number followed by space and unit)
H. severity level: type SEVERITY_LEVEL is (NOTE, WARNING, ERROR, FAILURE); => this is enumerated type defined in some lib.
II. Array: all array type can have optional "range of index". we can have single dim array, multi dim arry or array of arrays.
o. single dimensional array:
A. string: type STRING is array (POSITIVE range <>) of CHARACTER; => array of characters encloed in double quotes. ex: "hold time", "x" (array length=1). <> implies unconstrained range. So array range is positive integers (from 0 to max).
B: bit_vector: type BIT_VECTOR is array (NATURAL range <>) of BIT; => array of bits enclosed in double quotes. ex: "0010_0111", x"00FF" (_, B, X, O were added in vhdl-93)
array objects must be declared with index constraint. i.e
variable A: STRING(0 to 2122); =>A(0) to A(2122) are all valid assgn
variable C: BIT_VECTOR (3 downto 0);
aggregate for array: C :=('1','0','1','0'); => 4 bit positional aggregate.
we can also have c as C:="1010"; --since "1010" is array of character
or C := '1' & '0' & "10"; -- concatenation. each operand of & can be an array or element of an array.
o. Multi dimensional array: ex:
TYPE mem is array (0 to 1, 0 to 3) OF bit; => mem is 2 dim array, with 1st index(0 to 1) and 2nd index(0 to 3)
CONSTANT ROM: mem:= ( ('0','0','1','0'),
('1','1','0','0'));
when referencing: data_bit := ROM(1,2) => implies row 1 col 2, which has a value of "0"
o. Array of array: ex:
TYPE word is array (0 to 3) of BIT;
TYPE mem is array (0 to 1) of word;
variable data: word;
data := ROM(1) => data is assigned value "1100".
2. types in TEXTIO pkg: (PACKAGE TEXTIO is .... END TEXTIO;) => also see in file section below for usage
I. 2 data types for textio: text, line. used in process and subpgm
A. LINE: type LINE is access STRING;
B. TEXT: type TEXT is file of STRING;
II. std text files
A. FILE input: TEXT is in "STD_INPUT";
B. FILE output: TEXT is out "STD_OUTPUT";
III. I/O routines => various flavours of READ/WRITE supported depending on whether in/out is BIT_VECTOR, STRING,etc.
A. READLINE: procedure READLINE (F: in TEXT; L: out LINE);
B. READ: procedure READ (L: inout LINE; VALUE: out BIT_VECTOR);
C. WRITELINE: procedure WRITELINE (F: out TEXT; L: in LINE);
D. WRITE: procedure WRITE (L: inout LINE; VALUE: in BIT_VECTOR);
3A. extended type in IEEE std_logic_1164 pkg: ((PACKAGE IEEE is .... END IEEE;). For cadence, this file exists in /apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/ieee/std_logic_1164.vhdl. It has (PACKAGE std_logic_1164 IS ... END std_logic_1164). In any logic level sim, std_logic type are required as opposed to BIT type. This pkg extends bit from 2 values(0,1) to std_logic 9 values. So, we have to include std_logic_1164.all package in all vhdl files.
I. scalar
A. std_logic: type std_logic is ('U','X','0','1','Z','W','L','H','-'); U=uniniialized, X=unknown, Z=high impedance, W=weak unknown, while L=weak 0, H=weak 1. - is don't care.
II. vector
A. std_logic_vector: TYPE std_logic_vector IS ARRAY ( NATURAL RANGE <> ) OF std_logic; => array of std_logic elements enclosed in ". ex: "101Z". So, a(3 downto 0)="101Z" => a(3)='1', a(2)='0', a(1)='1', a(0)='Z'.
3B. extended type in IEEE numeric_std pkg: It defines 2 new types: signed, unsigned which are similar to std_logic_vector except that arith,comp,logical operators work on them. These operators will also work on std_logic_vector by including this pkg.
I. signed: type SIGNED is array (natural range <>) of std_logic; -- 0 to 2^n-1
ex: signal A_unsigned : unsigned(3 downto 0) ; A_unsigned <= "1111" assigns a value 15 to A_unsigned
II. unsigned: type UNSIGNED is array (natural range <>) of std_logic; -- -2^n-1 to 2^n-1 - 1
ex: signal B_signed : signed (3 downto 0) ; B_signed <= "1111" assigns a value -1 to B_signed
4. enumerated type => defines new type. should be defined before being used. Can be used in entity, architecture, block, process, procedure in section where we define signals, variables etc (before "begin" section). Best place for type declaration is either in package declaration or package body.
type SPI_STATE_TYPE is (COMMAND_WR_STATE, DATA_WR_STATE, DATA_RD_STATE); -- defines new type SPI_STATE_TYPE that can only take these 3 identifiers. During logic synthesis, an actual numeric coding is chosen for these 3 states (i.e 00, 01, 10) so if SPI_STATE_TYPE ever goes to 11, it would be invalid state. NOTE: in waveform of sims, we won't be able to see the value of SPI_STATE_TYPE as it's not a numeric value.
signal CURR_STATE, NEXT_STATE :SPI_STATE_TYPE => this defines these 2 fsm signals as one of the types in SPI_STATE_TYPE
subtype digit is integer range 0 to 9; => subtype are based upon existing type restricting them in some way. Now, we can declare variables/signals of this type:
variable MSD, NSD: digit; is equiv to
variable MSD, NSD: integer range 0 to 9;
#qualified expr: to cast a lieral to particular type
type' (lieral or expr)
ex: bit' ('1') => casts 1 to a bit type (1 could have been character, bit or std_logic)
ex: integer' (3.0) => cast 3.0 to integer 3
#aggregate
variable B: BIT;
variable C: BIT_VECTOR (8 downto 0);
C := BIT_VECTOR' ('1', 7 downto 3 =>B, others=>'0');
#File operations: 2 stage: identify logical file and read lines in file.
ex:
process (proc)
FILE infile: TEXT is in "path/test.data"; --identifies a logical file named infile of type text called test.data
FILE outfile: TEXT is in "path/out.data"; --files of type text are treated as group of lines
variable L1,L2:LINE; -- variables defined as type Line (1 line of text in a file)
variable av:bit_vector(3 downto 0); -- define 4 bit vectors
begin
WHILE NOT(ENOFFILE(infile)) LOOP
READLINE (infile,L1); -- readline procedure to read a line L1 from text infile
READ (L1,av); -- read procedure which reads an item off a particular line. here reads a 4 bit value from line L1
WRITE (L2,av); -- writes a 4 bit value to line L2
WRITELINE (outfile,L2); -- write line L2 to text outfile
END LOOP;
END process;
file test.data contains
0011
00_11 -- underscores are ignored
16#E# -- # indicates number in different radix. here 16# means in base 16, number is E
1010 0111
--------
conversion b/w different types: each element of signed/unsigned/std_logic_vector is still std_logic type. i.e A_unsigned(3) = '1' (std_logic type) in ex above. So, we need conversion, as vhdl is strongly typed.
1. signed/unsigned to/from intger: we need conversion functions:
A. std_numeric pkg:
To convert signed/unsigned into integer: to_integer() function
To convert integer to signed/unsigned: to_signed(signed_integer, width) or to_unsigned(unsigned_integer, width) function.
B. std_logic_arith pkg:
To convert signed/unsigned into integer: conv_integer() function
To convert integer to signed/unsigned: conv_signed(signed_integer, width) or conv_unsigned(unsigned_integer, width) function.
2. std_logic_vector to/from integer:
A. std_numeric pkg: 2 step process
To convert std_logic_vector into integer: to_integer(signed(A_slv)), to_integer(unsigned(A_slv)
To convert integer to std_logic_vector: std_logic_vector(to_signed(signed_integer, width))) or std_logic_vector(to_unsigned(unsigned_integer, width)) function.
B. std_logic_arith pkg: use vhdl type qualifier: leaving out ' is an error with this pkg.
To convert std_logic_vector into integer: conv_integer(signed'("1010")) => type cast 1010 to signed number -2. If we use pkg "std_logic_signed", then all std_logic_vector will be converted to signed, so explicit cast not required: conv_integer("1010");
To convert integer to std_logic_vector: std_logic_vector'(conv_signed(signed_integer, width))) or std_logic_vector'(conv_unsigned(unsigned_integer, width)))
ex: signal count: std_logic_vector(3 downto 0); count <= count + 1; => this won't work with STD pkg as count is std_logic_vector while "1" is integer (+ not allowed on std_logic). To make it work, we need to use std_numeric or std_logic_arith pkg:
std_numeric: count <= std_logic_vector(to_signed((to_integer(signed(count)) + 1)),4);
std_logic_arith: count <= count + 1; If pkg unsigned, then it's unsigned integer count added to 1. If pkg signed, then count is treated as 2's complement number and added to 1, result is a number in 2's complement
ex: + works on signed/unsigned.
signal A8, B8, Result8 : unsigned(7 downto 0) ;
Result8 <= A8 + B8 ; => this gives 8 bit result with no carry out
ex: "1011" > "0011": If pkg unsigned, then it's 11>3 => true. If pkg signed, then it's -5>3 => false. We can explicit conversion type too. i.e signed'("1011") > signed'("0011") => false.
ex: variable a,b,x: integer range 0 to 255; -- by default integer are 32 bit. By defining range, we make these 8 bit.
x:=a+b; -- 8 bit adder built. If range was not defined above, then 32 bit adder would be built
ex: to add carryin to sum, we do a trick to extend addition to 1 extra lsb bit: Y[3:0] = A[3:0] + B[3:0] + Cin;
Sum(4 downto 0) <= (A(3 downto 0) & '1') + (B(3 downto 0) & Cin) ; Y[3 downto 0) <= Sum(4 downto 1);
ex: signal A_uv, B_uv : unsigned( 7 downto 0) ; signal Z_uv : unsigned(15 downto 0) ;
Z_uv <= A_uv * B_uv; => result width is sum of the width of 2 i/p.
Z_uv <= A_uv * 2; => result width is still 16 bits
-----
OBJECTS: Any name or identifier in vhdl is an object, which can be of any scalar or array literal type described above. Every object needs to have a type, as vhdl is strongly typed lang. A name must begin with an alphabetic char, followed by letter, _, or digit. OBJECTS in vhdl need to have a "kind" associated that says when to update the value of that object. In normal pgm language, values of objects are updated immediately on execution, but in h/w lang, they may need to be updated differently.
Any named object can be of 2 kinds: fixed or varying in value.
1. Fixed: only 1 type:
A. constant: name assigned to a fixed value. Can be assigned to scaler or Array. Can be declared in package, entity, arch or subpgm, but usually declared in user defined packages.
ex: constant vdd: REAL :=-2.0; --scalar. vdd is REAL type with val=2.0
ex: constant FIVE: std_logic_vector (8 to 11) := "0101"; --array FIVE(8)=0, FIVE(9)=1 and so on.
2. Varying in value: 2 types:
A. variable: name assigned to changing value within a process. variable assgn occurs immediately in simulation. variable can be used to document a physical wire or as temp sim value. variables can be local (declared in a process) or global (to communicate b/w process, only in VHDL92). Note that variables are declared within process in contrast to signals which cannot be declared within process. variables are assigned using :=.
ex: variable COUNT: TIME range 10ns to 50ns :=20ns; -- scalar variable COUNT with initial value 20ns. default initial value is the leftmost value of that type(i.e fs in this ex?). Valid range of values is in between 10ns to 50ns only.
ex: variable MEM: BIT_VECTOR (0 to 7); -- index constraint is 0 to 7. so MEM(0) .. MEM(7) .. MEM(0 to 3) are all valid.
ex: variable x, y: INTEGER; x:= y+1; -- defaults to -2,147,483,647.
B. signal: connects design entities and communicates b/w process. signals can be used to document a physical wire. signals can be declared in 3 places: entity (as ports or global signals), arch (as local signals, declared before start of "begin ... end") or pkg(as global signals). signals can't be declared in a process, but can be used in a process. This is in contrast to variable which can be declared in process. However signal assgn within a process is delayed until a wait is executed. signals are assigned using <=. Examples of global/local signal decl are shown in Gemini code later. signals when declared as ports is shown next.
Range: we can define range constrint for variables and signals. We can also have index constraint for arrays.
ex: A in integer range 1 to 10 (or range 10 downto 1) => Note: range should be in compatible dirn with orig declaration of that type. Since, integer is defined from -ve to +ve, so we use small number to large number. Use downto to reverse dirn.
signals vs variables:
--------------------
signals and variables are no different when used within a process. Between processes, however we have to use signals.
We use variables within a process, when process is self contained. In such cases, we can define a variable "counter" and increment it within the process, to use it inside the process. It consumes less resources since the assignment happens immediately. signals consume more resources since they maintain their complete history and are only updated when a ¡°wait statement¡± or an inferred ¡°wait statement¡± (such as the end of a process that uses a sensitivity list) is reached. So, only signals can model seq behaviour of ckt. signals can be thought of as superset of variables. variable assgn using := is similar to blocking assgn of verilog. It synthesizes correctly, but is mostly used in testbenches. signal assgn using <= is similar to non-blocking assgn of verilog. So, for synthesizable logic, we exclusively use "signals".
Port syntax:
-----
port (names: dirn type [:=expr] [;more ports]); --expr is optional default initial value assigned to port.
#dirn is one of the 4 below:
1. In = RHS of variable or signal assgn (default is In when port dirn is not explicitly mentioned)
2. Out = LHS of signal assgn.
3. Inout = Both above (can have multiple drivers)
4. Buffer = Both above (can have 1 driver)
ex: ENTITY des is PORT (data_in: IN bit; data_out: OUT bit); end des; -- signals defined as ports
Note: a port defined with dirn Out can not be used internally in that entity. It can't be driving anything in that entity as that would mean it's not a pure output, but a Inout. If we try to compile with ncvhdl, we get error like:
ncvhdl_p: *E,RDOPRT : ports of mode OUT or LINKAGE may not be read 87[4.3.3] 93[4.3.2].
To fix this there are 2 options:
1. add -relax when running ncvhdl or irun.
2. We have to define the port as Inout or Buffer. But that would change the design, and some synthesis tools may have issues with Inout ports and buffer. so, better alternative is to declare an internal signal as *_INT, use this internal signal everywhere, and then assign this output port to the internal signal that can be read as well as assigned to output.
i.e. port(out_port: out std_logic); signal out_port_int: std_logic; some_sig <= out_port_int; out_port <= out_port_int; -- this works as out_port_int is defined as internal signal.
---
#operators (+,-,etc). operators manipulate objects and create new objects. Operators can only work on certain type (std_logic or real etc) of objects.
Most operators same as verilog, but vhdl doesn't have useful unary reduction op ( i.e |(val[7:0]). For this in vhdl, we've to use a "loop" stmt or do op on each bit. VHDL has "mod" op not found in verilog.
4 types of operators as shown below in terms of highest to lowest precedence:
1. Arithmetic operator: + - * / mod rem **(exponentiation) abs(absolute value). work on i/p types: integer, real. Package STANDARD doesn't allow arithmetic operators on bit_vector (or package std_logic_1164 on std_logic_vector). However, std_logic_arithmetic/numeric_std packages allow op on std_logic_vector, signed, unsigned. "-" operator creates a 2's complement in case of std_logic_vector.
NOTE: mod, rem only work on integer type. o/p type is same as i/p type, except in division o/p type is integer. These operators are also specification for synthesis tools to build logic.
2. concatenation operator: & concatenates.
For ex:
signal w, x, y, z :std_logic:='0';
signal t : std_logic_vector(1 downto 0);
t<= (w and x) & (y and z); => concatenates so that t(1) <= w and x; t(0) <= y and z;
3. relational operator: = /= < > <= >= . i/p type may be any scalar or 1-D array. o/p type is boolean. So may need conversion to std_logic if we want to assign it to a signal (which are usually std_logic type).
4. logical operator: not, xnor(in vhdl 92 only), xor, nor, nand, or, and. It takes 2 i/p operaand and returns one o/p operand. i/p type may be boolean(true/false), std_logic('0'/'1') or vectors of equal length. Does not work on integer. o/p type is same as i/p type. Note that even though logical operators are lowest in precedence, "not" has the highest precedence of all 4 types of operators.
ex: x <= a and b or c; -- not correct as it gives an error "illegal sequence of logical operators". Looks like and,or have same priority so that expr becomes ambiguous in absence of brackets. We don't know whether x=a and (b or c); or x=(a and b) or c; So we have to always use brackets so that there are atmost 2 logical operands, i.e x<=(a and b) or c;
5. shift operators (new VHDL92 operators): sll/srl (shift left/right logical), sla/sra (shift left/right arithmetic), rol/ror (rotate left/right logical).
ex: "1001" sll 3 = "1000" (since 0 gets shifted in from right/left for logical shift). For arithmetic shift, MSB/LSB bit are shifted in. For rotate, numbers are just rotated, so no new bits added.
------
#reserved keywords
1. open: can be used to connect to unused o/p. It implies it's floating
ex:
my_component : my_component
port map (
some_input => '0', -- unused i/p tied to 0
some_output(0)=> data(0),
some_output(1)=> open, -- unused o/p tied to open
);
2. loop/generate: to wrt code for seq of stmt that execute repeatedly. generate can be used outside of process, so it can be used to describe array of components.
L: for i in 1 to 10 loop
q(i) := a(i);
end loop; -- or "end loop L;"
L: for i in 0 to 7 generate
q(i) <= reg_wrt and REG_WR_DATA(i);
end generate; -- or "end generate L;"
L: for i in 0 to 3 generate
U: dff port map (x(i), clk, x(I+1)); -- this generates 4 flops in a serial shift register fashion
end generate; -- or "end generate L;"
3. process: all process run concurrently, though stmt within it are executed sequentially. Process is an infinite loop which runs forever. It starts from beginning once it reaches the end. wait will cause the process to get suspended until an event occurs on that signal. If there is no wait, then a single process will keep on running forever, since there's nothing to stop it.
#ending simulation
In VHDL, we don't have something similar to $finish as in verilog. The simulation will stop if there are no more pending transactions. Unfortunately, if there is a free running clock process in the test bench, this will never occur. Or a process, which doesn't doesn't have any sensitivity list (as in a testbench process where we set signals to different values at different times), such a process will get to the end, and then come back to start of process and repeat the process again (similar to what happens in clk process). So, to stop a simulation, we can specify it to run for a particular length of time (which is bad as this may change every time a change is made to the test bench) or stop the simulaion using an "assert" stmt when we are done with our testbench. We just set the severity to Failure to guarantee that the simulator will stop regardless of the simulator assert level settings. This is a common practice with VHDL simulations.
#assignment: concurrent (outside a process) or seqential (inside a process) assignments done to assign new values. Outside a process, we call it concurrent, because all these stmt seem to execute at same simulated time. Inside a process, stmt execute serially, so we call it seq assgn. However, if the assgn is using := then it happens immediately at that point in time. However, if it's using <= then the assginment is delayed until we reach the end of process, which has an implicit "wait" stmt. For ex if we have process(a,b,c) then implicit wait stmt is (wait on a,b,c) at end of process. signal assgn using <= are done after wait is executed.
In a process, last assgn to o/p is what counts. process in an infinite loop, which only waits at the end of the loop, if there is a sensitivity list, otherwise it doesn't have any wait stmt at the end. For ex, in clk generation logic, there is no sensitivity list, so there is no wait stmt at end, implying the process will run forever.
clock generation:
process -- NOTE: no sensitivity list, so process runs forever
begin
wait for (PERIOD/2); -- wait for half cycle. We need to initialize CLK to 0 at time 0, else CLK will always be X.
CLK <= not CLK;
end process
#########################################
Ex: various examples
1. D latch:
architecture behv of D_latch is
begin
process(data_in, enable)
begin
if (enable='1') then
-- no clock signal here
data_out <= data_in;
end if;
end process;
end behv;
2. DFF: D flip flop
architecture behv of dff is
begin
process(resetz, clock) -- data_in is not provided in sensitivity list, as it's seq logic
begin
if resetz='0' then -- brackets not necessary
data_out <= '0';
elsif (clock='1' and clock'event) then -- clock rising edge. Or use rising_edge(clock)
data_out <= data_in;
end if;
end process;
end behv;
3. combinatorial logic: AND gate
architecture behv of AND_GATE is
begin
process(A,B) -- or use process(all) in VHDL-2008
begin
F1 <= A and B; -- behavior des.
end process;
end behv;
4. combinatorial logic: MUX written as seq and concurrent
A. sequential
architecture behv1 of Mux is
begin
process(I3,I2,I1,I0,S)
begin
O <= "ZZZ"; -- default o/p assgn so that if we leave the "others" condition below, the logic won't synthesize to latch
case S is -- use case statement
when "00" => O <= I0;
when "01" => O <= I1;
when "10" => O <= I2;
when "11" => O <= I3;
when others => O <= "ZZZ";
end case;
end process;
end behv1;
B. concurrent
architecture behv2 of Mux is
begin
O <= I0 when S="00" else -- use when.. else statement
I1 when S="01" else
I2 when S="10" else
I3 when S="11" else
"ZZZ";
end behv2;
------------------------------------------------------
VHDL file format:
----------------
every design unit is compiled and put in library as a "component", which can be used in any other design.
A design unit has 4 kinds of declaration in it's vhdl file: package, entity, architecture, configuration. Each of these 4 are compiled separately and for each design unit, library will contain these 4 design units: compiled package, entity, architecture and configuration. package and configuration are optional. configuration unit (if present) decides which arch of design unit is to be run (if no configuration specified, then latest complied arch of that entity is used).
Top level vhdl file for Gemini project (in /db/MOTGEMINI_DS/design1p0/HDL/Source/):
spi.vhd => this file just defines all lib.
---------
#declare lib IEEE so that we can open it and access pkg std_logic_1164, numeric_std.
library IEEE;
use IEEE.std_logic_1164.all; => this needed for std_logic and std_logic_vector. see above.
use IEEE.numeric_std.all; => this is preferred over std_logic_arith.
#user defined pkg SPI_TYPEDEFS (in file spi_typedefs.vhd which has user defined constants) in user defined lib SPI_LIB.
library SPI_LIB;
use SPI_LIB.SPI_TYPEDEFS.all;
ENTITY => defines entity with signal port defn. similar to module defn in verilog.
---------
entity SPI is -- top level entity is SPI
generic ( -- similar to parameter in verilog. Different inst of SPI can use diff values of genric via port map.
c2q_delay : time := 1ns; -- default value provided so that
width: integer := 7 -- Note no semicolon
);
Port (
SPI_CS_N : In std_logic; -- all i/o ports defined
SPI_SO : Out std_logic;
SPARE_D_B5 : Out std_logic -- Note no semicolon
);
end SPI;
ARCHITECTURE => define internal logic od that entity. Can have multiple arch with different names.
------------------
architecture SCHEMATIC of SPI is
#define all signals and components here before starting the body "begin .. end architecture_name".
signal DATA_RD : SPI_DATA_TYPE; -- define signals (wires) to be used in this arch to connect components. SPI_DATA_TYPE is defined in pkg SPI_TYPEDEFS as std_logic_vector(7 downto 0)
....
signal WREN : std_logic; -- defined as type std_logic
type mem is array (0 to num) of std_logic_vector (63 downto 0 ); //mem[num-1:0][63:0]
function write_fsm ( ...)
begin ...
end write_fsm;
procedure init_mem (...)
begin ...
end init_mem;
-- define components (modules) to be instantiated in this block as vhdl needs module defn, before instantiation of a module. In verilog, we could directly instantiate a module, as long as that module was present in some file. We have to do this, since in vhdl each entity gets compiled as a separate "component", so we need to tell simulator about existence of such components. We instantiate SPI_REGS later as: I_SPI_REGS : SPI_REGS Port Map ( ... )
NOTE: In VHDL-93, components are not necessary. we can directly instantiate an enity within an arch as: I_SPI_REGS : entity work.SPI_REGS Port Map ( ... )
component SPI_REGS -- component syntax is exactly same as entity
generic (width: integer :=7); -- this genric defined in entity defn of SPI_REGS
Port (
ADDR : In SPI_ADDR_TYPE; -- declare all i/o ports for SPI_REGS.
...
SPARE_D_B5 : Out std_logic
);
end component;
#begin architecture body
begin
fsm_U0: process (...) -- process defn. similar to always in verilog
begin
...
end process; -- or "end process fsm_U0;"
SPI_REGS_1 : SPI_REGS -- Instance of component SPI_REGS is named SPI_REGS_1
generic map (width => 7) -- NOTE: no semicolon or comma here
Port Map (
ADDR => ADDR_1, -- Port ADDR of SPI_REGS is mapped to ADDR_1 pin of SPI_REGS_1
SPARE_D_B5 => SPARE_D_B5
);
SPI_CONTROL_1 : SPI_CONTROL
Port Map ( ...
);
#end arch stmt
end SCHEMATIC;
CONFIGURATION
--------------------
configuration CFG_SPI_SCHEMATIC of SPI is -- cfg name is CFG_SPI_SCHEMATIC
for SCHEMATIC -- arch being configured
for SPI_REGS_1: SPI_REGS -- cfg component instance SPI_REGS_1 of component SPI_REGS
use configuration WORK.CFG_SPI_REGS_BEHAVIORAL; -- says that for SPI_REGS component, use cfg CFG_SPI_REGS_BEHAVIORAL of SPI_REGS component (defined in spi_regs.vhd)
end for;
for SPI_CONTROL_1: SPI_CONTROL -- for SPI_CONTROL component
use configuration WORK.CFG_SPI_CONTROL_BEHAVIORAL; -- (defined in spi_control.vhd)
end for;
end for;
end CFG_SPI_SCHEMATIC;
--------------------------------------------
Other files
---------
spi_typedefs.vhd
-----------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
#pkg declaration
package SPI_TYPEDEFS is
constant ACTIVE : std_logic := '0';
constant INACTIVE : std_logic := not ACTIVE;
constant SPI_DATA_BITS : integer := 8;
subtype SPI_DATA_TYPE is std_logic_vector(SPI_DATA_BITS - 1 downto 0); -- so we can access individual array element by using SPI_DATA_TYPE(0), SPI_DATA_TYPE(1), ..., SPI_DATA_TYPE(7).
signal ground: bit:=0; -- global signal declared to be used anywhere.
end SPI_TYPEDEFS;
---------
spi_regs.vhd
-----------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
library SPI_LIB;
use SPI_LIB.SPI_TYPEDEFS.all;
#entity
entity SPI_REGS is
Port (
ADDR : In SPI_ADDR_TYPE;
SPARE_D_B5 : Out std_logic
);
SIGNAL sys_clk: bit :='1'; -- this is global signal as it's declared in entity.
end SPI_REGS;
#arch
architecture BEHAVIORAL of SPI_REGS is
signal BG_REG : std_logic_vector(3 downto 0); -- this is local signal as declared within arch
....
signal SPARE_RD : std_logic_vector(7 downto 0);
#no component declaration here as all componenets used (std gates, logic, etc) are in a pkg.
begin
#logic
RESET <= ACTIVE when (DIG_RESET_N = '0' or PWR_ON_RST_N = '0') else -- assigns 0 to Reset if expr true else Reset assigned 1.
INACTIVE;
CHIP_REV <= (CHIP_REV_B3, CHIP_REV_B2, CHIP_REV_B1, CHIP_REV_B0); -- chip_rev is defined as std_logic_vector(3 downto 0);
#define write process
WRITE_PARAMS: -- process label
process(RESET,SPI_SCK) -- sensitivity list
variable ADDR_INT : integer range 0 to 2**SPI_ADDR_BITS - 1; -- variables defined
begin
if (RESET = ACTIVE) then
BG_REG <= (3 => '1', others => '0'); -- implies BG_REG[3]=1 everything else =0
elsif rising_edge(SPI_SCK) then
if (WREN = ACTIVE) then
case to_integer(ADDR) is
when ADDR_00 => BG_HOLD_REG <= DATA_WR(3 downto 0);
when others => null; -- default case
end case;
end if;
end if;
end process;
#define read process
READ_PARAMS:
process(ADDR, ...) -- sensitivity list much larger since no clk, so data directly rd out.
variable POINTER : integer range 0 to DATA_RD'high;
begin
DATA_RD <= (others => '0'); -- DATA_RD[7:0] assigned value of all 0.
case to_integer(ADDR) is
when ADDR_00 => DATA_RD(BG_REG'range) <= BG_REG; -- range specifies the range
when ADDR_10 => DATA_RD(5 downto 0) <= MTR_OFST_REG;
DATA_RD(6) <= MTR_OFST_TRM_ENBL_REG;
when others => null; -- default case
end case;
end process;
#o/p assignments
ENBL_AGC_LEAK <= ENBL_AGC_LEAK_REG;
end BEHAVIORAL;
#configuration
configuration CFG_SPI_REGS_BEHAVIORAL of SPI_REGS is
for BEHAVIORAL
end for;
end CFG_SPI_REGS_BEHAVIORAL;
-------------
spi_control.vhd : similarly for control
------------
Running vhdl sims:
-------------------
To run sims, we need digtop_tb.vhd (similar to digtop_tb.v). 2 ways:
1. verilog tb: We have veriog file digtop_tb.v. In digtop_tb.v, we define a module "testbench" with no ports, define internal signals, creates clocks, assign initial values to required signals, and then instantiate the dut "digtop". We create a "initial begin .. end" block which provides patterns on specific io pins at varying times, and then have a $finish to finish the sim.
2. vhdl tb" we create tb_spi.vhd. It has empty entity (tb entity similar to tb module in verilog) defined, then architecture defn. Within architecture, we've internal signals to connect dut, then component "digtop" defined. then inside body of architecture, we've digtop instatntiation as "dut" with ports connected, and then multiple process to create clock, and read/write.
library IEEE; ...
entity E is == empty entity
end E
Architecture A of E is
signal SPI_CS_local : std_logic ....
component digtop Port (SPI_CS: In std_logic; ....); end component;
begin
DUT: digtop Port Map (SPI_CS => SPI_CS_local, ...);
TB: block
constant ...;
signal ...;
function FUNC_1 (STR: in string) return boolean is begin ... end FUNC_1;
#cmd interpretor process to read stimulus file and then finish the sim by asserting false (equiv to $finish)
cmd_intr: process variable a .. begin .. read stimulus file ..loop thru each line .. at reading stim file
assert FALSE report "stimulus complete" severity FAILURE;
end process
#clk genrator process: creates a clk of freq TB_CLK_PERIOD
CLK_GENERATOR: process begin
TB_CLK <= '1'; wait for TB_CLK_PERIOD/2;
TB_CLK <= '0'; wait for TB_CLK_PERIOD/2;
end process;
#spi_read, spi_wrt proces. has diff procedure inside process.
SPI_RD: process ... end process;
end block;
end A;
configuration CFG1 of E is
for A
for DUT : digtop
-- use configuration WORK.CFG_SPI_SCHEMATIC; => we don't use configuration for gate level netlist as digtop is verilog netlist, so no concept of configuration for verilog netlist.
end for;
for TB
end for;
end for;
end CFG1;
now, we run "irun" providing all .vhd files (exactly similar as in verilog sims).
---------------
Compile (analyze) files
---------------
To compile VHDL files, we put all src files in Source dir. Then when you start dc-shell, it creates a WORK dir for you, which is the default WORK lib for VHDL.
Then, if we want to analyze and store analyzed (compiled) results in user specified design lib, we use analyze cmd with -library option to put all compiled files in SPI_LIB dir.
dc_shell_t > analyze -format vhdl $RTL_DIR/spi_typedefs.vhd -library SPI_LIB
--------
cadence VHDL predefined package: included by default when running RC.
/apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/std/standard.vhdl
/apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/ieee/std_logic_1164.vhdl
/apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/cadence/attributes.vhdl
/apps/cds/rc/10.1-s202/tools.lnx86/lib/vhdl/ieee/numeric_std.vhdl
std_logic_1164.vhdl:
--------------------
std logic file for cadence also has lot of conversion functions and basic gate functions defined. VHDL being strongly typed, conversion is needed from one type to other type. Ex:
A. and gate function
SUBTYPE UX01 IS resolved std_ulogic RANGE 'U' TO '1'; -- ('U','X','0','1') only 4 states defined
FUNCTION "and" ( l : std_ulogic; r : std_ulogic ) RETURN UX01;
FUNCTION "and" ( l : std_ulogic; r : std_ulogic ) RETURN UX01 IS
BEGIN
RETURN (and_table(l, r)); => and table defined below
END "and";
-- truth table for "and" function, o/p is always 1 of these 4 values => U,X,0,1
CONSTANT and_table : stdlogic_table := (
-- ----------------------------------------------------
-- | U X 0 1 Z W L H - | |
-- ----------------------------------------------------
( 'U', 'U', '0', 'U', 'U', 'U', '0', 'U', 'U' ), -- | U |
( 'U', 'X', '0', 'X', 'X', 'X', '0', 'X', 'X' ), -- | X |
( '0', '0', '0', '0', '0', '0', '0', '0', '0' ), -- | 0 |
( 'U', 'X', '0', '1', 'X', 'X', '0', '1', 'X' ), -- | 1 |
( 'U', 'X', '0', 'X', 'X', 'X', '0', 'X', 'X' ), -- | Z |
( 'U', 'X', '0', 'X', 'X', 'X', '0', 'X', 'X' ), -- | W |
( '0', '0', '0', '0', '0', '0', '0', '0', '0' ), -- | L |
( 'U', 'X', '0', '1', 'X', 'X', '0', '1', 'X' ), -- | H |
( 'U', 'X', '0', 'X', 'X', 'X', '0', 'X', 'X' ) -- | - |
);
B. conversion functions:
FUNCTION To_StdULogic ( b : BIT ) RETURN std_ulogic; => func declaration
FUNCTION To_StdULogic ( b : BIT ) RETURN std_ulogic IS => function body
BEGIN
CASE b IS
WHEN '0' => RETURN '0';
WHEN '1' => RETURN '1';
END CASE;
END;
numeric_std.vhdl:
-----------------
implements std 1076.3 of IEEE. defines numeric types and arithmetic functions (+,-,*,/,rem,mod,<,>,=,/=,>=,<=) for use with synthesis tools. Two numeric types are defined: (The base element type is type STD_LOGIC)
UNSIGNED: represents UNSIGNED number in vector form
SIGNED: represents a SIGNED number in vector form (rep in 2's complement)
package numeric_std is
function "+" (L, R: UNSIGNED) return UNSIGNED; => function declaration
function ADD_UNSIGNED (L, R: UNSIGNED; C: STD_LOGIC) return UNSIGNED is => called in function "+"
constant L_LEFT: INTEGER := L'LENGTH-1;
alias XL: UNSIGNED(L_LEFT downto 0) is L;
alias XR: UNSIGNED(L_LEFT downto 0) is R;
variable RESULT: UNSIGNED(L_LEFT downto 0);
variable CBIT: STD_LOGIC := C;
begin
for I in 0 to L_LEFT loop
RESULT(I) := CBIT xor XL(I) xor XR(I);
CBIT := (CBIT and XL(I)) or (CBIT and XR(I)) or (XL(I) and XR(I));
end loop;
return RESULT;
end ADD_UNSIGNED;
function "+" (L, R: UNSIGNED) return UNSIGNED is
-- cadence synthesis BUILTIN_OPERATOR
constant SIZE: NATURAL := MAX(L'LENGTH, R'LENGTH);
variable L01 : UNSIGNED(SIZE-1 downto 0);
variable R01 : UNSIGNED(SIZE-1 downto 0);
begin
if ((L'LENGTH < 1) or (R'LENGTH < 1)) then return NAU;
end if;
L01 := TO_01(RESIZE(L, SIZE), 'X');
if (L01(L01'LEFT)='X') then return L01;
end if;
R01 := TO_01(RESIZE(R, SIZE), 'X');
if (R01(R01'LEFT)='X') then return R01;
end if;
return ADD_UNSIGNED(L01, R01, '0');
end "+";
---
-------------------------------
generic:
------
generic can be set in 2 ways:
ex: entity flea is
generic ( PreLoadFile: STRING := "/dev/null";
MODE: BOOLEAN := FALSE;
VAL: std_logic_vector (7 downto 0) := "01011001");
port ( ... );
1. override using genric (similar to defparam): similar to verilog, we cannot do
generic a_cont_deg.SREG_SIZE = 3; // NOT allowed in vhdl
2. override during instantiation: similar to verilog. NOTE: tfilter is vhdl, but this file is verilog, so exact verilog syntax can be used for this file to override generics
ex: tfilter #(.MODE(1),.PreLoadFile(my.txt),.VAL(8'b0010_1011)) a_cont_deg (.reset(..), ...);
3. via irun cmdline:
//since designs are usually mix of verilog/vhdl, we need to use . for navigating verilog hier and : for vhdl hier.
irun -generic 'veridian_tb.u_h1.BANK_U0:PreLoadFile=>"flash.img"' -generic "veridian_tb.u_h1.BANK_U0:VAL=>11000011" \ => here everything upto u_h1 is verilog, but BANK_U0 is vhdl, so we use : for BANK_U0 but . for everything before then. NOTE: "" or '' not really necessary above. i.e this works too:
irun -generic veridian_tb.u_h1.BANK_U0:PreLoadFile=>"flash.img" -generic veridian_tb.u_h1.BANK_U0:VAL=>11000011
NOTE: if using it in csh script, we may want to define whole generic thing as variable. We do it as follows:
set BANK0 = "-generic veridian_tb.u_h1.BANK_U0:PreLoadFile=>"'"flash_boot.img"' //NOTE '. pair of '' protects all special char within it from shell interpretation. pair of "" allows shell to interpret ! and $.
set VAL = "-generic veridian_tb.u_h1.BANK_U0:VAL=>00101001"
irun ${BANK0} ${VAL} ...
------------------------