Difference between revisions of "VHDL tutorial"

From UConn PAN
Jump to navigation Jump to search
 
(17 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
{| align="right" border="1"
 +
|
 +
{| align="right" width="100px" style="text-align:center" cellspacing="0"
 +
! colspan="2" style="background:#ffff66" | VHDL Tutorial
 +
|-
 +
| colspan="2" style="background:#ffff99" | A brief guide to VHDL design with a design example; the introduction and core of the tutorial.
 +
|-
 +
| style="background:#ffff66" | < prev
 +
| style="background:#ffff66" | [[VHDL: Where to start|next >]]
 +
|}
 +
|}
 +
 
FPGA programming using a hardware description language is not a commonly taught skill in physics programs, but is a necessary skill for designing the electronics required for this project.  This tutorial aims to layout the design process and teach the basics of hardware description language; in particular [http://en.wikipedia.org/wiki/Vhdl VHDL].  The main competitor to VHDL is [http://en.wikipedia.org/wiki/Verilog Verilog]; tutorials and information regarding Verilog can be found through Google web searching.
 
FPGA programming using a hardware description language is not a commonly taught skill in physics programs, but is a necessary skill for designing the electronics required for this project.  This tutorial aims to layout the design process and teach the basics of hardware description language; in particular [http://en.wikipedia.org/wiki/Vhdl VHDL].  The main competitor to VHDL is [http://en.wikipedia.org/wiki/Verilog Verilog]; tutorials and information regarding Verilog can be found through Google web searching.
  
 
== Design example ==
 
== Design example ==
  
To illustrate the discussions in this tutorial, a design example is discussed along the way.  The design example is the [[Programming_the_FPGA#Emulator_.28D.29|emulator for the AD5535 DAC]].  As each step of the design process is discussed, the DAC emulator will be used for illustration.
+
To illustrate the discussions in this tutorial, a design example is discussed along the way.  The design example is the [[Programming_the_DAC#Emulator|emulator for the AD5535 DAC]].  As each step of the design process is discussed, the DAC emulator will be used for illustration.
 
 
== Where to start ==
 
 
 
As any good engineer will tell you, design twice and code once.  As any bad engineer will tell you, design once and code eighteen times.  Learn to love white boards, erasers, and engineering paper.  I learned this the hard way, so do yourselves a favor and just humor me.
 
 
 
=== The black box ===
 
 
 
The first part of the design process is completely independent of any code.  The first step is to define the "black box" of your circuit; that is, draw a box and say what goes in and what comes out.  VHDL allows three types of ''pins'' (connections to the outside world):
 
* '''in''': An ''in'' pin can be read from but never written to.
 
* '''out''': An ''out'' pin can be written to but never read from.
 
* '''inout''': An ''inout'' pin can be both read from and written to, providing the flexibility to allow bidirectional communication on a single line.  At first this seems the ideal choice and that you would always want inout pins; in actual fact you want to avoid inout pins unless you absolutely need them for bidirectional communication.
 
 
 
Some notes on nomenclature and notation:
 
* The term '''signal''' is sometimes used to generically refer to a line, pin, or bus.  Later you will see that the ''proper'' use of the term signal is to discuss an internal line or bus.  However, as pins are connected to internal lines and buses, sometimes sloppy terminology extends "signal" to include pins.
 
* Signals come in '''active-high''' and '''active-low''' varieties.  Active-high means that a logical 1 is "on" and a logical 0 is "off".  It is also known as "positive logic".  Active-low is the exact opposite; logical 0 is "on" and logical 1 is "off", and it is alternately known as "negative logic".
 
* Some signals serve double-duty.  As active-high logic they perform one operation, but as active-low logic they perform another operation.  This links these operations as complimentary pairs.  For example a shift register may shift its output or it may load a new value.  If it's not shifting then it's loading.  So the name would be something like "Shift/Load" to signify that shifting is active-high and loading is active-low.  Carrying over this notation, any signal that is written "/Name" is an active-low signal.  This notation is not always used, but it is quite common and I shall attempt to maintain this practice throughout the tutorial.  When writing on paper, an active-low signal is frequently denoted by an overbar instead of a leading slash.  Unless otherwise specified, a signal that is not marked as active-low is assumed to be active-high by default.  Some designs assume active-low as the default, but that will either be marked or implied by context (i.e. all active-high lines marked as such).
 
* A '''pin''' is an input, output, or inout.  A '''line''' is a single pin or a single bit of data flowing along a wire.  A '''bus''' is properly a multidrop line, but through common usage (due to the way circuits are commonly designed), in digital logic at this level the term has come to mean multiple lines bound together into a bundle.  On a diagram, a bus appears as a thick line with a slash through it.  Near the slash will be a number denoting how many lines are bundled into that bus.
 
 
 
=== Example: the block box ===
 
 
 
For the DAC emulator, the inputs are clearly defined for us.  The AD5535 data sheet discusses the [[Programming_the_FPGA#Interface_.28D.29|serial interface protocol]] in detail.  We need four input lines:
 
* ''/Reset'': an asynchronous, active-low reset line
 
* ''D_in'': serial data line
 
* ''/Sync'': an active-low flag to begin transmission
 
* ''SClk'': a serial clock
 
 
 
Our outputs are not so clearly defined.  This emulates the circuit itself, so the output of the black box is purely for our benefit to aid in testing.  So I decided to define as outputs 32 channels, each 14-bits wide, which display the value being fed to each DAC at any given time.
 
 
 
=== The block diagram ===
 
 
 
Having defined your block box, you need to fill in your black box.  But before doing that, we need to note the different between two types of logic:
 
* ''Combinational logic'' merely recombines lines into new lines.  For example, signal Q may be the logical AND of signals A, B, and C.  There is no reference to a clock in combinational logic.
 
* ''Sequential logic'' is any logic that makes use of a clock for latches, flip-flops, registers, or other devices.  Sequential logic changes only when the clock changes.  Often circuits are wired so that all sequential logic changes together, either on a ''falling edge'' of a clock or a ''rising edge'' of a clock.  Advanced designs can change some components on a rising edge and other components on a falling edge, but this is significantly more difficult due to the tighter timing restrictions imposed.
 
A "good" design does its best to separate combinational and sequential logic.  All combinational logic takes time; each gate has a delay associated with it.  Highly complex combinational logic can take long enough to skew the timing of your circuit; some lines will clock in too late and will take effect on the following clock cycle, completely ruining your synchronization and causing sporadic or faulty behavior in your circuit.  For this reason it is best to separate the two types of logic as best you can, although it is not always possible to fully separate them.  Generally the sequential block will feed the combinational block, and the combinational block will loop back to the sequential block for any required feedback or recursion.
 
 
 
Bearing this in mind, you need to partition your design into functional blocks.  Each functional block will be a new black box within the larger design, with two well-defined attributes: I/O pins and functionality.  Sometimes these '''functional block diagrams''' will become layered, with a functional block in the top-most diagram having a functional block diagram describing its own internals.  For complex designs, there can be many layers and many engineers, so that each engineer is only working on a small subset of the components so that the I/O ports and functionality must be precisely defined and followed so that integration of the components requires a minimum of component redesign.  The functional block diagram shows the I/O ports of the overall design (if you have no inouts, it is conventional to put inputs on the left and outputs on the right at all times to clarify data flow), all blocks (with I/O ports labeled), and signals connecting each block as appropriate.
 
  
Notes:
+
== The tutorial ==
* Since clock lines are required for most designs (every sequential block needs one), most engineers no longer write the world "clock" or even the abbreviation "CLK" on a block diagram.  Instead it is understood that a clock line is represented by a small carat or divot on the side of the block (often placed in the top left corner of the block, but that is not a requirement).
 
* Many devices are general purpose, so giving a descriptive label to a pin would be pointless as the description will change depending on the application.  A common shorthand for such blocks is to use "D" as the input signal and "Q" as the output signal.  This is often seen on registers, multiplexers, flip-flops, etc.
 
* Pens are the devil.  Click erasers are a divine blessing.  Engineering paper (or just plain old graph paper) is practically a holy artifact.  You ''will'' draw this diagram several times (especially as you try to route lines across the paper and realize you left too much space on one side and not enough on the other), so draw it out in pencil then trace over in pen once you're satisfied (for particularly large designs that can be the point where you break your last pencil in half and decide its not worth driving to the store for more).
 
  
=== Example: the block diagram ===
+
Due to the length of the tutorial, it had to be broken into several pagesHere are the links to the various sections of the tutorialThe first three sections discuss VHDL itselfThe final section is about using the development environment provided by Xilinx; you can read this section first or last as you see fit.
 
 
[[Image:DAC_Emulator_Block.JPG|thumb|Functional block diagram of the DAC emulator.]]
 
 
 
The block diagram is shown to the right, and each block is described on the [[Programming_the_FPGA#Emulator_.28D.29|FPGA programming page]]Note that the repeated blocks (the 32 terminal registers) are not all drawnTwo or three are generally sufficient to illustrate connections (for example, does a line feed all 32, or are there 32 separate lines?)Ellipses are perfectly acceptable.
 
 
 
== Enter the code monkey ==
 
  
 +
* [[VHDL: Where to start]] - Section one of the tutorial, focusing on preparing your design for coding.
 +
* [[VHDL: Enter the code monkey]] - Section two of the tutorial, focusing on outlining the framework of your code.
 
:''See also: [http://en.wikipedia.org/wiki/Code_monkey code monkey]''
 
:''See also: [http://en.wikipedia.org/wiki/Code_monkey code monkey]''
 +
* [[VHDL: The real code]] - Section three of the tutorial, focusing on coding the body of your design.
 +
* [[VHDL: Xilinx ISE]] - Section four of the tutorial, focusing on using the development environment.
  
=== Some basics ===
+
== Extras ==
  
Now we lay down some actual code.  First things first: comments!  If you've ever done any programming, you know how wonderful comments are.  If you've not done much coding, then remember that comments are not as pointless as they seem.  Add descriptive comments or be murdered in your sleep 15 years from now by an irate engineer who has to decipher your legacy code.  VHDL has no block comments, only line comments (the comment goes from the comment marker to the end of the line).  The comment marker is a double dash with no spacing between them.  Many development environments (for example Xilinx ISE) will auto-generate a large block of comments at the top of each file to be used to describe the file (who, what, where, when, why, how, and so on).
+
Here is some extra information regarding VHDL to be used as reference material.
  
Secondly are the libraries.  These are like the include statements in C/C++.  Honestly I forget which libraries have exactly what tools in them, but a standard block will cover mostly any design you work on:
+
=== VHDL Resolution Table ===
  
<pre>
+
VHDL STD_LOGIC and STD_LOGIC_VECTOR both operate on 9-value logic defined by IEEE.  The nine states are:
-- Lines beginning with "--" are comments
+
* U: uninitialized
-- This standard block will cover you for most designs.
+
* X: forcing unknown
-- Some development environments will auto-generate library "use" statements that may or may not include this set
+
* 0: forcing 0
library IEEE;
+
* 1: forcing 1
use IEEE.STD_LOGIC_1164.ALL;
+
* Z: high impedance
use IEEE.STD_LOGIC_ARITH.ALL;
+
* W: weak unknown
use IEEE.STD_LOGIC_UNSIGNED.ALL;
+
* L: weak 0
</pre>
+
* H: weak 1
 
+
* &#8211;: don't care
=== Coding your black box ===
 
 
 
Every VHDL file defines an '''entity'''.  Every entity has two parts to it: a ''port list'' and an ''architecture''.  The port list defines the black box of your component.  First you declare that you are defining an entity, then inside the entity you declare your ports.
 
 
 
As discussed above, there are three types of ports: in, out, inout.  There are also lines and buses.  A line is referred to as a '''STD_LOGIC''' and a bus is referred to as a '''STD_LOGIC_VECTOR'''.  There are also two flavors of STD_LOGIC_VECTORS: '''downto''' and '''to'''.  A "downto" bus has the most significant bit (MSB) associated with the largest subscript, and a "to" bus has the least significant bit (LSB) associated with the largest subscript.  A bus with N lines need not have subscripts running from 0 ''to'' N-1.  They can run from 1 ''to'' N or N+84 ''downto'' 85; there is complete freedom as to the subscript offset.  Most people who learned coding in C or Java or something similar will generally stick to 0 ''to'' N-1 or N-1 ''downto'' 0 out of habit unless the situation calls for something else (for clarity, usually).  Often an engineer will choose either "to" or "downto" (I happen to prefer "downto") and stick with it.  However mixing "to" and "downto" has it's uses; for example connecting a "downto" to a "to" will reverse the order of the bits in the bus.  For a beginner, I recommend picking one and sticking to it.
 
 
 
<pre>
 
entity component_name is
 
    Port ( line_in : in STD_LOGIC;                      -- a single input line
 
          bus_in1 : in STD_LOGIC_VECTOR (7 downto 2);  -- a 6-bit "downto" input bus
 
          bus_in2 : in STD_LOGIC_VECTOR (0 to 8);      -- a 9-bit "to" input bus
 
          line_out : out STD_LOGIC;                    -- a single output line
 
          bus_inout : inout STD_LOGIC_VECTOR (1 to 10) -- a 10-bit bidirectional bus
 
        );
 
end component_name;
 
</pre>
 
 
 
Note that each port in the list is separated by a semicolon, but the last port does not have a semicolon after it.  The semicolons in this case are not end-of-line markers, but are list delimiters.
 
 
 
=== Example: coding your black box ===
 
 
 
The library use statements and port list for the DAC emulator are shown here.  Note the terribly non descriptive comments for the input lines.  This kind of comment is not only useless (as it merely repeats the signal name), but is mocking the pain of later engineers who have to figure out what a signal like ''invSYNC'' might do.  This will result in ninjas attacking your home.  To avoid this fate, I recommend better comments such as, "an active-low flag to begin transmission," for ''invSYNC'' or, "serial clock line," for ''SCLK''.
 
 
 
<pre>
 
library IEEE;
 
use IEEE.STD_LOGIC_1164.ALL;
 
use IEEE.STD_LOGIC_ARITH.ALL;
 
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
 
 
entity DAC_emulator is
 
    Port ( SCLK     : in STD_LOGIC; -- SCLK
 
          invRESET : in STD_LOGIC; -- /RESET
 
          invSYNC  : in STD_LOGIC; -- /SYNC
 
          D_in     : in STD_LOGIC; -- D_in
 
  -- 32 14-bit output channels
 
          ch00 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch01 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch02 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch03 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch04 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch05 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch06 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch07 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch08 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch09 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch10 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch11 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch12 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch13 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch14 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch15 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch16 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch17 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch18 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch19 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch20 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch21 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch22 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch23 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch24 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch25 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch26 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch27 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch28 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch29 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch30 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch31 : out  STD_LOGIC_VECTOR (13 downto 0)
 
    );
 
end DAC_emulator;
 
</pre>
 
 
 
=== Filling the black box ===
 
 
 
Now that you have your black box, you need to fill it in.  The interior of the box is called the '''architecture'''.  First you declare that you are making an architecture and what it is an architecture of.  Then inside of there you have two sections: declaring your components, signals, and variables, and the actual control flow of the circuit.
 
 
 
Declarations come in three types (that I know of; there may be more):
 
* '''Components''' are other blocks that you are including in your circuit.  Inside of a component is another circuit, which may contain still further components.
 
* '''Signals''' are internal signals.  They are the wires connecting all of your components.  As with pins they come in STD_LOGIC and STD_LOGIC_VECTOR, but they do not have I/O polarity as they are strictly internal.  As a general rule you attach a signal to a pin so that you can either read from or write to that signal.
 
* '''Variables''' are like variables in a normal software language.  They can only be used inside of a ''process'' (which I will discuss later).  Often in simple designs you can work around using variables and stick with just signals.  Unfortunately, you'll have to do a bit of online research to find out much detail about variables, as I hardly use them in these designs.
 
Components and signals should all be declared up front in the declaration section.  As I mentioned, you can only use a variable inside of a process, so you would declare the variable within the process.
 
 
 
The framework for the architecture is shown here.
 
 
 
<pre>
 
architecture arch_name of component_name is
 
    signal line_sig : STD_LOGIC;                    -- an internal signal line
 
    signal bus_sig : STD_LOGIC_VECTOR (4 downto 2); -- a 3-bit internal signal bus
 
begin
 
    --------------------
 
    -- code goes here --
 
    --------------------
 
end arch_name;
 
</pre>
 
 
 
As you can see, declarations happen before the "begin" and code goes after the "begin".  Even if you have no signals, components, or variables to declare, you must include the "begin" statement.
 
 
 
You may have noticed that I did not show a component.  Components require more discussion and will be demonstrated later.  Because the DAC emulator uses components, I will discuss that code later.  First we will discuss the components inside of the emulator and the final product will come later.
 
 
 
== The real code ==
 
 
 
Now that we've framed the whole thing, you're probably wondering what the actual code is.  Now we're going to discuss that.  We'll go through how the code works, showing examples from the DAC emulator components, and eventually build up to the full emulator.
 
 
 
=== A very important warning ===
 
 
 
The first thing you have to understand is that VHDL is '''not''' software.  It is a hardware description language.  Many people look at VHDL as a software tool to program the hardware and run into all sorts of problems.  Remember to think hardware.  The main pitfall is demonstrated in this bit of code:
 
 
 
<pre>
 
statement_1;
 
statement_2;
 
for i = 1 to 5
 
    statement_3;
 
statement_4;
 
</pre>
 
 
 
A software engineer looks at this and thinks it's quite simple.  First to execute is ''statement_1'', followed by ''statement_2''.  Then the loop is executed: the first iteration, followed by the second, all the way down to the fifth being the last part of the loop.  Finally ''statement_4'' is executed and the program terminates.  In software you'd be correct.
 
 
 
Now let's consider how this would run in VHDL.  Very simply: everything happens ''at once''.  If ''statement_1'' modifies signal X from 7 to 19 and ''statement_2'' reads signal X, what value will ''statement_2'' read?  '''7'''.  Not 19, because the two statements are simultaneous.  The same is true of the loop.  Iteration 1 comes no sooner than (and no later than) iteration 5.  So you need to keep that in mind: everything is simultaneous, regardless of where and how you type it into the code.  Forget that and you may never figure out why your circuit is trying to eat itself.
 
 
 
=== The basics: combinational logic ===
 
 
 
Let's start simple.  Combinational logic just takes the lines and mixes them together to create a new set of lines.  You have the whole range of usual logical operators: AND, OR, NOR, NAND, XOR, XNOR, NOT, and whatever else you can dream up and assemble.  To see an example of combinational logic at work, take a look at the code for the 5-to-32 demultiplexer included in the emulator.
 
 
 
<pre>
 
library IEEE;
 
use IEEE.STD_LOGIC_1164.ALL;
 
use IEEE.STD_LOGIC_ARITH.ALL;
 
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
 
 
entity DAC_demux is
 
    Port ( S : in  STD_LOGIC_VECTOR (4 downto 0);    -- 5-bit select
 
          D : in  STD_LOGIC;                        -- data to demux
 
          Q : out  STD_LOGIC_VECTOR (31 downto 0)); -- 32 output lines
 
end DAC_demux;
 
 
 
architecture DAC_demux_arch of DAC_demux is
 
begin
 
-- combinational logic to implement demux
 
Q(0) <= D and not (S(4)) and not(S(3)) and not(S(2)) and not(S(1)) and not(S(0));
 
Q(1) <= D and not (S(4)) and not(S(3)) and not(S(2)) and not(S(1)) and S(0);
 
Q(2) <= D and not (S(4)) and not(S(3)) and not(S(2)) and S(1) and not(S(0));
 
Q(3) <= D and not (S(4)) and not(S(3)) and not(S(2)) and S(1) and S(0);
 
Q(4) <= D and not (S(4)) and not(S(3)) and S(2) and not(S(1)) and not(S(0));
 
Q(5) <= D and not (S(4)) and not(S(3)) and S(2) and not(S(1)) and S(0);
 
Q(6) <= D and not (S(4)) and not(S(3)) and S(2) and S(1) and not(S(0));
 
Q(7) <= D and not (S(4)) and not(S(3)) and S(2) and S(1) and S(0);
 
Q(8) <= D and not (S(4)) and S(3) and not(S(2)) and not(S(1)) and not(S(0));
 
Q(9) <= D and not (S(4)) and S(3) and not(S(2)) and not(S(1)) and S(0);
 
Q(10) <= D and not (S(4)) and S(3) and not(S(2)) and S(1) and not(S(0));
 
Q(11) <= D and not (S(4)) and S(3) and not(S(2)) and S(1) and S(0);
 
Q(12) <= D and not (S(4)) and S(3) and S(2) and not(S(1)) and not(S(0));
 
Q(13) <= D and not (S(4)) and S(3) and S(2) and not(S(1)) and S(0);
 
Q(14) <= D and not (S(4)) and S(3) and S(2) and S(1) and not(S(0));
 
Q(15) <= D and not (S(4)) and S(3) and S(2) and S(1) and S(0);
 
Q(16) <= D and S(4) and not(S(3)) and not(S(2)) and not(S(1)) and not(S(0));
 
Q(17) <= D and S(4) and not(S(3)) and not(S(2)) and not(S(1)) and S(0);
 
Q(18) <= D and S(4) and not(S(3)) and not(S(2)) and S(1) and not(S(0));
 
Q(19) <= D and S(4) and not(S(3)) and not(S(2)) and S(1) and S(0);
 
Q(20) <= D and S(4) and not(S(3)) and S(2) and not(S(1)) and not(S(0));
 
Q(21) <= D and S(4) and not(S(3)) and S(2) and not(S(1)) and S(0);
 
Q(22) <= D and S(4) and not(S(3)) and S(2) and S(1) and not(S(0));
 
Q(23) <= D and S(4) and not(S(3)) and S(2) and S(1) and S(0);
 
Q(24) <= D and S(4) and S(3) and not(S(2)) and not(S(1)) and not(S(0));
 
Q(25) <= D and S(4) and S(3) and not(S(2)) and not(S(1)) and S(0);
 
Q(26) <= D and S(4) and S(3) and not(S(2)) and S(1) and not(S(0));
 
Q(27) <= D and S(4) and S(3) and not(S(2)) and S(1) and S(0);
 
Q(28) <= D and S(4) and S(3) and S(2) and not(S(1)) and not(S(0));
 
Q(29) <= D and S(4) and S(3) and S(2) and not(S(1)) and S(0);
 
Q(30) <= D and S(4) and S(3) and S(2) and S(1) and not(S(0));
 
Q(31) <= D and S(4) and S(3) and S(2) and S(1) and S(0);
 
end DAC_demux_arch;
 
</pre>
 
 
 
You can see the use statements, the entity declaration, and the framework we previous discussed for the architecture.  You see that there are no signals or components declared for the architecture.  Then you see a whole list of combinational logic.  You can see that the logical operators AND and NOT are used extensively.  Also note the indexing for the STD_LOGIC_VECTORs.  There is something which appears to be an assignment operator: '''<='''.  This is not called an assignment; it is verbalized as "flows to" (or "flows from") to emphasize the hardware nature of VHDL.  So take the last line:
 
 
 
<pre>
 
Q(31) <= D and S(4) and S(3) and S(2) and S(1) and S(0);
 
</pre>
 
 
 
The most significant bit of the ''Q'' bus flows from the logical AND of line ''D'' and all lines in bus ''S''.  Pretty simple, right?  There is an order of operations to VHDL (feel free to run a Google search), but you can enforce your own ordering through the use of parentheses.
 
 
 
Any questions?  Hopefully not, because we're moving on.
 
 
 
=== The next step: sequential logic ===
 
 
 
Now we're moving into the realm of sequential logic.  As you may recall, that means we're adding a clock.  This is also where you usually add conditional statements (if, case, etc) and loops (for, etc).  So we get to add a new idea: the '''process'''.  I mentioned processes before, because that's where variables live.  Any time you have conditional statements or loops, they must be enclosed within a process.  A process tells VHDL that some signal(s) need to be watched and that this block needs to be updated if those signals change.  The list of signals to watch is called the "process sensitivity list".  Like an architecture, a process has a framework (giving it a name and a sensitivity list), a declaration region, and a code region.  Let's look at the code for the shift register in our emulator:
 
 
 
<pre>
 
library IEEE;
 
use IEEE.STD_LOGIC_1164.ALL;
 
use IEEE.STD_LOGIC_ARITH.ALL;
 
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
 
 
entity DAC_shifter is
 
    Port ( SCLK     : in  STD_LOGIC; -- clock
 
          invRESET : in  STD_LOGIC; -- asynchronous, active-low reset
 
          Enable  : in  STD_LOGIC; -- shift enable
 
          D_in    : in  STD_LOGIC; -- serial in
 
          Addr    : out STD_LOGIC_VECTOR (4 downto 0); -- address portion of output (5MSB)
 
          Code    : out STD_LOGIC_VECTOR (13 downto 0)); -- code portion of output (14LSB)
 
end DAC_shifter;
 
 
 
architecture DAC_shift_arch of DAC_shifter is
 
    signal SReg : STD_LOGIC_VECTOR (18 downto 0); -- internal resistor value
 
    signal delayed : STD_LOGIC;
 
      -- delay serial input due to delay caused by 19-cycle hold block
 
begin
 
    delayer : process (SCLK, D_in, delayed) -- delay serial input
 
    begin
 
        if (rising_edge(SCLK)) then
 
            delayed <= D_in;
 
        else
 
            delayed <= delayed;
 
        end if;
 
    end process delayer;
 
 
 
    shift_reg : process (SCLK, invRESET, Enable, D_in)
 
    begin
 
        if (invRESET = '0') then
 
            -- asynchronous, active-low reset
 
            SReg <= "0000000000000000000";
 
        else
 
            if (Enable = '1') then
 
                if (rising_edge(SCLK)) then
 
                    -- shift up when shift is enabled
 
                    for i in 0 to 17 loop
 
                        SReg(i+1) <= SReg(i);
 
                    end loop;
 
                    SReg(0) <= delayed;
 
                else
 
                    SReg <= SReg;
 
                end if;
 
            else
 
                SReg <= SReg;
 
            end if;
 
        end if;
 
    end process shift_reg;
 
 
    Addr <= SReg(18 downto 14); -- split parallel output
 
    Code <= SReg(13 downto 0);  -- split parallel output
 
end DAC_shift_arch;
 
</pre>
 
 
 
So what do we see?  Well, a whole lot.  So let's start small:
 
  
<pre>
+
If you have two or more drivers for the same line, then VHDL must somehow resolve the conflict.  The resolution table is given below.
delayer : process (SCLK, D_in, delayed)  -- delay serial input
 
begin
 
    if (rising_edge(SCLK)) then
 
        delayed <= D_in;
 
    else
 
        delayed <= delayed;
 
    end if;
 
end process delayer;
 
</pre>
 
 
 
Okay, now what?  Well, this is a process.  It is called "delayer".  After the name we say it's a process and give the sensitivity list.  That means that if ''SCLK'', ''D_in'', or ''delayed'' changes, then the synthesizer needs to check this process again to see if anything else changes.  We had to put this inside of a process because it makes use of the if-else construction.  You can see the format of the if-else:
 
 
 
<pre>
 
if (condition) then
 
    statement_1
 
else
 
    statement_2
 
end if;
 
</pre>
 
 
 
You also see a shorthand that has been pre-defined for you: rising_edge.  There is also falling_edge.  These combine the ''event'' command with a specification of which direction the event happened (transition up or transition down).  Sequential logic makes extensive use of rising_edge and falling_edge.  Also note the else clause.  What is the point of having ''delayed'' loop back on itself?  This is to help the synthesizer along.  VHDL has some quirks and doesn't appreciate an if without an else.  So even if nothing happens, you always want to put that else clause in there and have all the signals in question loop back on themselves.  Otherwise the synthesizer goes crazy and adds latches left and right which at best make your design hideously large and unwieldy and at worst make your design completely nonfunctional.
 
 
 
Now let's take a look at the second process.
 
 
 
<pre>
 
shift_reg : process (SCLK, invRESET, Enable, D_in)
 
begin
 
    if (invRESET = '0') then
 
        -- asynchronous, active-low reset
 
        SReg <= "0000000000000000000";
 
    else
 
        if (Enable = '1') then
 
            if (rising_edge(SCLK)) then
 
                -- shift up when shift is enabled
 
                for i in 0 to 17 loop
 
                    SReg(i+1) <= SReg(i);
 
                end loop;
 
                SReg(0) <= delayed;
 
            else
 
                SReg <= SReg;
 
            end if;
 
        else
 
            SReg <= SReg;
 
        end if;
 
    end if;
 
end process shift_reg;
 
</pre>
 
 
 
This one is a little more complicated.  First we see that the ''invRESET'' signal takes effect outside of the rising_edge condition.  That makes it an asynchronous signal; that is, it takes effect instantly instead of waiting for the next clock cycle to happen.  Then inside of the rising_edge condition we see a for loop.  The format of such a loop is
 
 
 
<pre>
 
for i in a to b loop
 
    statements
 
end loop;
 
</pre>
 
 
 
Here ''i'' is actually a variable (and can be named anything you want it to be named).  However it is a read-only variable, so don't bother trying to write to it.  The bounds of the loop are set by ''a'' and ''b'', which must be static at this point in the program.
 
 
 
So how are you feeling about all of this code?  Take a look at the other components and see if you can figure out what they're doing as some practice:
 
* '''Add a link to DAC_demux.vhd'''
 
* '''Add a link to DAC_follow.vhd'''
 
* '''Add a link to DAC_hold19.vhd'''
 
* '''Add a link to DAC_shifter.vhd'''
 
* '''Add a link to DAC_register.vhd'''
 
 
 
=== Putting it together: components ===
 
 
 
Now let's start to assemble this mess.  But first a question: why make everything into components in separate files?  Why not just cut and paste the code from each file into the body of the emulator?  We most certainly could have done that.  There's absolutely no problem with it.  However by making components we get several advantages:
 
* Clarity of design: We can easily compare the VHDL code and the schematic it generates to the functional block diagram and see what each block does and how it connects.  Otherwise we'd have this huge mess of gates and flip-flops all over the place and not the foggiest idea what any of it does or why it's there (or if some of it is extra garbage the synthesizer added because we messed up the code).
 
* Reusability: Recall that I said a while back that many components are general purpose sorts of things that you can use over and over again in different designs.  Well, if I design each functional block as a separate component then I can just add that component to another design.  In fact exactly that happened: the DAC_hold19 block came in handy for the DAC controller.  I just had to include the component in that design because it was already built and tested.
 
* Multiplicity: Recall the functional block diagram.  How many terminal registers where there?  Thirty-two.  Do you want to type (or cut & paste) the code for a register 32 times?  Me neither.  I make one component and include it 32 times.  And if there's an error, I only have to fix one set of code, not make the same correction 32 times.
 
Having said that, feel free to ignore me and just put every process into one file; the code will run just the same, the FPGA will be programmed just the same.  It's a convenience to the designer, not a requirement.
 
 
 
So how do we add components?  There are two steps: the first is to declare the component and the second is to map the component.  For this discussion, take a look at the full code of the emulator.
 
 
 
<pre>
 
library IEEE;
 
use IEEE.STD_LOGIC_1164.ALL;
 
use IEEE.STD_LOGIC_ARITH.ALL;
 
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
 
 
entity DAC_emulator is
 
    Port ( SCLK    : in STD_LOGIC; -- SCLK
 
          invRESET : in STD_LOGIC; -- /RESET
 
          invSYNC  : in STD_LOGIC; -- /SYNC
 
          D_in    : in STD_LOGIC; -- D_in
 
  -- 32 14-bit output channels
 
          ch00 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch01 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch02 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch03 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch04 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch05 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch06 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch07 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch08 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch09 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch10 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch11 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch12 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch13 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch14 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch15 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch16 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch17 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch18 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch19 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch20 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch21 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch22 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch23 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch24 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch25 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch26 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch27 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch28 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch29 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch30 : out  STD_LOGIC_VECTOR (13 downto 0);
 
          ch31 : out  STD_LOGIC_VECTOR (13 downto 0));
 
end DAC_emulator;
 
 
 
architecture DAC_emul_arch of DAC_emulator is
 
    -- declare component wrappers
 
    component DAC_demux
 
    Port ( S : in  STD_LOGIC_VECTOR (4 downto 0);
 
D : in  STD_LOGIC;
 
Q : out  STD_LOGIC_VECTOR (31 downto 0));
 
    end component;
 
 
 
    component DAC_follow
 
        Port ( CLK      : in  STD_LOGIC;
 
invReset : in  STD_LOGIC;
 
D        : in  STD_LOGIC;
 
Q        : out STD_LOGIC);
 
    end component;
 
 
 
    component DAC_hold19
 
        Port ( CLK      : in  STD_LOGIC;
 
invReset : in  STD_LOGIC;
 
invBegin : in  STD_LOGIC;
 
Go      : out STD_LOGIC);
 
    end component;
 
 
    component DAC_shifter
 
        Port ( SCLK    : in  STD_LOGIC;
 
invRESET : in  STD_LOGIC;
 
Enable  : in  STD_LOGIC;
 
D_in    : in  STD_LOGIC;
 
Addr    : out STD_LOGIC_VECTOR (4 downto 0);
 
Code    : out STD_LOGIC_VECTOR (13 downto 0));
 
    end component;
 
 
    component DAC_register
 
        Port ( CLK    : in  STD_LOGIC;
 
invRST : in  STD_LOGIC;
 
Enable : in  STD_LOGIC;
 
D      : in  STD_LOGIC_VECTOR (13 downto 0);
 
Q      : out STD_LOGIC_VECTOR (13 downto 0));
 
    end component;
 
 
    -- declare internal signals to connect components
 
    signal Enable : STD_LOGIC;
 
    signal Reg_Enable : STD_LOGIC;
 
    signal Reg32_Enable : STD_LOGIC_VECTOR (31 downto 0);
 
    signal Addr : STD_LOGIC_VECTOR (4 downto 0);
 
    signal Code : STD_LOGIC_VECTOR (13 downto 0);
 
begin
 
    -- port maps to create component instances
 
    u1: DAC_demux  port map (Addr, Reg_Enable, Reg32_Enable);
 
    u2: DAC_follow  port map (SCLK, invReset, Enable, Reg_Enable);
 
    u3: DAC_hold19  port map (SCLK, invReset, invSync, Enable);
 
    u4: DAC_shifter port map (SCLK, invReset, Enable, D_in, Addr, Code);
 
    u500: DAC_register port map (SCLK, invReset, Reg32_Enable(0),  Code, ch00);
 
    u501: DAC_register port map (SCLK, invReset, Reg32_Enable(1),  Code, ch01);
 
    u502: DAC_register port map (SCLK, invReset, Reg32_Enable(2),  Code, ch02);
 
    u503: DAC_register port map (SCLK, invReset, Reg32_Enable(3),  Code, ch03);
 
    u504: DAC_register port map (SCLK, invReset, Reg32_Enable(4),  Code, ch04);
 
    u505: DAC_register port map (SCLK, invReset, Reg32_Enable(5),  Code, ch05);
 
    u506: DAC_register port map (SCLK, invReset, Reg32_Enable(6),  Code, ch06);
 
    u507: DAC_register port map (SCLK, invReset, Reg32_Enable(7),  Code, ch07);
 
    u508: DAC_register port map (SCLK, invReset, Reg32_Enable(8),  Code, ch08);
 
    u509: DAC_register port map (SCLK, invReset, Reg32_Enable(9),  Code, ch09);
 
    u510: DAC_register port map (SCLK, invReset, Reg32_Enable(10), Code, ch10);
 
    u511: DAC_register port map (SCLK, invReset, Reg32_Enable(11), Code, ch11);
 
    u512: DAC_register port map (SCLK, invReset, Reg32_Enable(12), Code, ch12);
 
    u513: DAC_register port map (SCLK, invReset, Reg32_Enable(13), Code, ch13);
 
    u514: DAC_register port map (SCLK, invReset, Reg32_Enable(14), Code, ch14);
 
    u515: DAC_register port map (SCLK, invReset, Reg32_Enable(15), Code, ch15);
 
    u516: DAC_register port map (SCLK, invReset, Reg32_Enable(16), Code, ch16);
 
    u517: DAC_register port map (SCLK, invReset, Reg32_Enable(17), Code, ch17);
 
    u518: DAC_register port map (SCLK, invReset, Reg32_Enable(18), Code, ch18);
 
    u519: DAC_register port map (SCLK, invReset, Reg32_Enable(19), Code, ch19);
 
    u520: DAC_register port map (SCLK, invReset, Reg32_Enable(20), Code, ch20);
 
    u521: DAC_register port map (SCLK, invReset, Reg32_Enable(21), Code, ch21);
 
    u522: DAC_register port map (SCLK, invReset, Reg32_Enable(22), Code, ch22);
 
    u523: DAC_register port map (SCLK, invReset, Reg32_Enable(23), Code, ch23);
 
    u524: DAC_register port map (SCLK, invReset, Reg32_Enable(24), Code, ch24);
 
    u525: DAC_register port map (SCLK, invReset, Reg32_Enable(25), Code, ch25);
 
    u526: DAC_register port map (SCLK, invReset, Reg32_Enable(26), Code, ch26);
 
    u527: DAC_register port map (SCLK, invReset, Reg32_Enable(27), Code, ch27);
 
    u528: DAC_register port map (SCLK, invReset, Reg32_Enable(28), Code, ch28);
 
    u529: DAC_register port map (SCLK, invReset, Reg32_Enable(29), Code, ch29);
 
    u530: DAC_register port map (SCLK, invReset, Reg32_Enable(30), Code, ch30);
 
    u531: DAC_register port map (SCLK, invReset, Reg32_Enable(31), Code, ch31);
 
end DAC_emul_arch;
 
</pre>
 
 
 
In the declaration section, near the signals, you can see the component declarations:
 
 
 
<pre>
 
component component_name
 
    ---------------------------------------------------------
 
    -- copy the port list from the entity declaration here --
 
    ---------------------------------------------------------
 
end component;
 
</pre>
 
 
 
It's that simple!  Just make sure that the component name and the port list match between the component declaration here and the entity declaration in the other VHDL file, and you're all set.  Now to map the component:
 
 
 
<pre>
 
u531: DAC_register port map (SCLK, invReset, Reg32_Enable(31), Code, ch31);
 
</pre>
 
 
 
You give the component an identifier.  Customarily engineers use the U numbering system (U followed by a number, starting at 1 and going until you run out of components).  But you can give the component map any identifier you see fit.  Then you give the component name, the words "port map", then a list of signals or pins.  The signal listed first in the port map will be connected to the pin listed first in the component declaration, and so on down the line.  And that's all you have to do to include components in your design.  The synthesizer will retrieve the appropriate code and make the connections for you.
 
 
 
 
 
 
 
== VHDL Resolution Table ==
 
  
 
{| style="text-align:center"
 
{| style="text-align:center"
|+VHDL Resolution Table
+
|+ '''VHDL Resolution Table'''
 
|-
 
|-
 
!  !! U !! X !! 0 !! 1 !! Z !! W !! L !! H !! &#8211;
 
!  !! U !! X !! 0 !! 1 !! Z !! W !! L !! H !! &#8211;
Line 579: Line 79:
 
|}
 
|}
  
VHDL Logic States
+
=== Links ===
* U: uninitialized
+
 
* X: forcing unknown
+
In case you can't follow the near-incoherent ramblings that constitute my tutorial, here are links to some others.  And always remember: Google is a programmer's best friend.
* 0: forcing 0
+
* http://esd.cs.ucr.edu/labs/tutorial/
* 1: forcing 1
+
* http://www.vhdl-online.de/tutorial/
* Z: high impedance
 
* W: weak unknown
 
* L: weak 0
 
* H: weak 1
 
* &#8211;: don't care
 

Latest revision as of 18:55, 17 July 2007

VHDL Tutorial
A brief guide to VHDL design with a design example; the introduction and core of the tutorial.
< prev next >

FPGA programming using a hardware description language is not a commonly taught skill in physics programs, but is a necessary skill for designing the electronics required for this project. This tutorial aims to layout the design process and teach the basics of hardware description language; in particular VHDL. The main competitor to VHDL is Verilog; tutorials and information regarding Verilog can be found through Google web searching.

Design example

To illustrate the discussions in this tutorial, a design example is discussed along the way. The design example is the emulator for the AD5535 DAC. As each step of the design process is discussed, the DAC emulator will be used for illustration.

The tutorial

Due to the length of the tutorial, it had to be broken into several pages. Here are the links to the various sections of the tutorial. The first three sections discuss VHDL itself. The final section is about using the development environment provided by Xilinx; you can read this section first or last as you see fit.

See also: code monkey
  • VHDL: The real code - Section three of the tutorial, focusing on coding the body of your design.
  • VHDL: Xilinx ISE - Section four of the tutorial, focusing on using the development environment.

Extras

Here is some extra information regarding VHDL to be used as reference material.

VHDL Resolution Table

VHDL STD_LOGIC and STD_LOGIC_VECTOR both operate on 9-value logic defined by IEEE. The nine states are:

  • U: uninitialized
  • X: forcing unknown
  • 0: forcing 0
  • 1: forcing 1
  • Z: high impedance
  • W: weak unknown
  • L: weak 0
  • H: weak 1
  • –: don't care

If you have two or more drivers for the same line, then VHDL must somehow resolve the conflict. The resolution table is given below.

VHDL Resolution Table
U X 0 1 Z W L H
U U U U U U U U U U
X U X X X X X X X X
0 U X 0 X 0 0 0 0 X
1 U X X 1 1 1 1 1 X
Z U X 0 1 Z W L H X
W U X 0 1 W W W W X
L U X 0 1 L W L W X
H U X 0 1 H W W H X
U X X X X X X X X

Links

In case you can't follow the near-incoherent ramblings that constitute my tutorial, here are links to some others. And always remember: Google is a programmer's best friend.