
\section{Overview}
This section of the report describes the operation and implementation
of the video portion of the project.  To generate the video signals, a
Motorola MC6847 video display generator was used in External
Alphanumeric graphics mode.  The chip was controlled by a FLEX 10K70
FPGA, which also handled the formatting of data received on a 3-way
8-bit parallel bus.  The video was displayed to an FTC-1201-R 12''
color CRT display at a resolution of 192 pixels high by 256 pixels
wide.
 
\section{Operation}
The job of the video module of this project is to display the input
and output data, as well as useful operational data.  The screen is
divided into three sections.  Along the right side is a sidebar which
displays information on the current filter mode and variable settings.
The rest of the screen is split into the top and bottom halves.  The
top half displays the frequency response of the input signal and the
bottom half displays the frequency response of the output signal,
which will have been processed by the selected filter.

The video information is stored in two 8Kx8 SRAMs which are muxed such
that one is being written to while the other is being read from.  The
purpose of such a setup is so that one frame of information can be
held for as long as necessary before switching to the next frame.
This allows data processing and transfer to take more than the
standard time allotted for a single frame in the NTSC standard, which
the video display complies with.  To prevent visible flickering, the
frame is only changed during the vertical blanking period.

There are no direct user controls for the video module other than a
reset button, which clears the state of the video FSMs.  All control
is dependent upon the data transmission cycles across the parallel
BUS.  Information on the screen is only updated when enough new
information has been acquired, in particular, after the end of a full
data transmission cycle.  Because the job of the video module is to
simply display the information it is given, there is no need for any
elaborate control interface.

\section{Design}
\subsection{Overview}
A detailed circuit diagram of the video controller module can be seen
in Figure~\ref{vidblockdiagram}.  One thing of note is that while the
MC6847 is clocked at 3.579545 MHz, all other synchronous components
are clocked to an external 10 MHz crystal oscillator.  There is no
need to worry about timing conflicts since the SRAM is asynchronous
and each memory module is either being read from by the MC6847
(3.579545 MHz) or written to by the video FSMs (10 MHz).  The analog
portions of the circuit are clearly visible on the bottom half.
Within the FPGA component are the three different FSM: the read FSM,
the ram FSM, and the write FSM, as well as a dual-port memory buffer.
These will be described later in this section.


\begin{figure}[bpht]
\begin{center}
\includegraphics[height=8in]{vidblockdiagram}
\end{center}
\caption{Detailed block diagram of the video module}
\label{vidblockdiagram}
\end{figure}


The video display cycle is dependent on the flow of information across
the parallel bus.  The steps of the cycle are as follows.  First, the
unprocessed frequency samples are received from the parallel bus.
After that, the processed frequency samples from the previous cycle
are received across the parallel bus.  Because the data is transmitted
in the form of real and complex components, the magnitude must be
calculated.  For ease, we chose to display the square of the
magnitude.  The main reason for this choice is that it allows us to
omit a complex square-root circuit, which would only take up precious
space on the FPGA without adding any meaningful functionality to the
video module.  After the magnitudes are calculated, the samples must
still be time averaged, since there are 1024 frequency samples and
only 256 pixels across the width of the screen (only 192 pixels for
the actually viewing area of the frequency response, since the right
side of the screen is occupied by the ``taskbar'').  When proper
values are finally calculated, the samples are written to SRAM in such
a way as to properly display on the monitor when the SRAM is being
read from.

The main components of the video module are the MC6847 video display
generator, the two 8Kx8 SRAM modules, the monitor, the analog
components, the dual-port memory buffer, and the three FSMs --- the
read FSM, the ram FSM, and the write FSM.  These can all be seen in
the simplified block diagram in Figure~\ref{bigblockdiagram}.

\begin{figure}[pbht]
\begin{center}
\includegraphics[width=6in]{bigblockdiagram}
\end{center}
\caption{Highly simplified block diagram showing the relationship between the major components}
\label{bigblockdiagram}
\end{figure}



\subsection{Clock}
Aforementioned, there are two clocks in the video module.  Because of
the availability of the 10 MHz clock built into the project kits, we
decided to use the 10 MHz signal as the standard clock.  Each project
module had its own clock, which affected our data communication
protocol, as will be explained later.  Specific to the video module is
a 3.579545 MHz clock, which was created from a chain of inverters and
a crystal.  This clock is needed for the MC6847, which must run at
that speed in order to produce an output compliant with NTSC
standards.

\subsection{Monitor}
The monitor is a 12'' CRT display capable of 8 or 16 color modes.  For
this project, however, we only use 2 colors (monochrome) displayed at
a resolution of 256 dots by 192 dots.  The monitor accepts as input
intensity, red, green, blue, horizontal sync, and vertical sync.  Since
we are using only two colors, the red, green, and blue lines are all
tied to the same line, which comes from the Y output after it is
passed through a LM357 op-amp. The intensity line is left unconnected.

\subsection{MC6847 Video Display Generator}
The MC6847 Video Display Generator is used to generate the appropriate
Hsync, Vsync, and RGB outputs to the monitor.  Of the different modes
available, we chose to use the external alphanumerics mode.  With the
external mode, we could create our own graphics by writing directly to
the external character ROM.  We did not chose a full graphics mode
since we still wanted to retain the option of switching to the
internal character mode to utilize the built in character ROM for
displaying text.  Because of switching time issues, we deemed that we
would not even use any of the semigraphics modes that are somewhat
compatible with the full text modes.  The semigraphics mode divides up
the normal 8 x 12 character blocks into smaller blocks which are
colored depending on the values in external RAM.  While the
semigraphics modes offer the option of 4 or even 8 colors, we decided
that color was not important to our project, as the color would add
nothing significant to the overall project.

The use of the external alphanumerics mode required that an external
row counter be created using a 74LS161 chip.  This chip would count
from 0 to 11, and would consist of the bottom four address bits when
accessing memory.  Since the full space taken up by a character is 8
dots wide by 12 dots high, the counter is needed to count through the
12 rows of bits each.  The upper addresses then correspond to
locations of 8x12 blocks, while the bottom four bits correspond to the
row within the 8x12 block.  The upper address lines are supplied
by the pins DA[8:0].  The MC6847 counts through these
sequentially, allowing it to read the data in an orderly fashion.

The display itself will have two screen sections.  The right screen,
which will be 64 dots by 192 dots, will contain information, such
as the current filter mode, and other important data, such as the
value and name the knobs that set the filter parameters.  The left
screen will by 192 dots by 192 dots.  This screen will be split into a
top and bottom section of equal size.  The top section, a 96 x 192
rectangle, will display the frequency information in the form of a
solid line graph.  The bottom section will also show frequency
information, but of the processed data.

\subsection{Analog circuitry}
The analog portion of the video module can be divided into three
parts.  The first part is the simple circuit used to make the 3.579545 MHz
clock.  A crystal and two inverters were placed in a loop, and then
inverted once more (also parallel to each inverter is a resistor).
The other two parts are the circuitry generating the RGB output and
the circuitry generating the VSync.  The HSync is generated directly
from the MC6847.  The Y pin of the MC6847 contained the output that
was required; we did not need the other output pins since we were
working in monochrome, where a pixel is either on or off.  The
output on the Y pin is wired to an op-amp to adjust the voltage to a
range appropriate for the monitor.  A 3.3k potentiometer provides a
means of adjusting the threshold voltage, directly affecting the
intensity of the signal on the monitor.  The MC6847 generates a
vertical clearing pulse instead of a VSync signal, so a resetable
one-shot is used to change the pulse into a VSync signal.  Two 50k
potentiometers allow control of the VSync to fine tune the picture.

\subsection{Read FSM}
The read FSM handles communication over the parallel bus as well as
the conversion of the frequency samples from real and complex
components into magnitude squared.  The state diagram below in
Figure~\ref{readfsmdiagram} shows the operation of the read FSM.

\begin{figure}[pbht]
\begin{center}
\includegraphics[width=6in]{readfsmdiagram}
\caption{Read FSM state diagram}
\label{readfsmdiagram}
\end{center}
\end{figure}

The FSM stays in an idle state until a valid signal goes high.  The
valid signal is an XOR of the FFTsend and PROCsend lines.  When the
FFTsend line is high, unprocessed frequency data is incoming from the
FFT unit; when the PROCsend line, processed and filtered frequency
data is incoming from the processing unit.  An XOR was used to avoid
the invalid state when both the FFTsend and PROCsend lines are high,
which should never happen.  The valid signal is high, then, whenever
any data is incoming on the parallel bus.

After receiving a valid signal, the FSM will go into a wait state
where it waits for a dataready signal to go high.  When FFTsend is
high, the FSM will consider FFTready to be the dataready signal; when
the PROCsend line is high, the FSM will consider the PROCready line to
be the dataready signal.  Upon receiving a dataready signal, the FSM
transitions to the read state, where it stores the data, increments a
counter, and acknowledges the a successful reception of data by
sending ACK high.  The FSM then waits for dataready to go low once
more before setting the ACK line low and returning to the wait state.

In the read state, if the FSM sees the lowest bit of the counter to be
0, it will store the data as complex\_r, since it will be a real
component of a frequency sample.  If the FSM sees that count(0) = 1,
though, the FSM stores the incoming data at complex\_i, and outputs the
result of ((complex\_r * complex\_r) + (complex\_i + complex\_i)) on the
complexout output line, which is connected to the ram FSM.  This cycle
repeats until all data is sent, when the PROCsend and FFT send lines
both go low.  The FSM then waits for about 16 cycles before going back
to the idle state.

A start signal is also asserted during the wait and read
state --- basically whenever the FSM is active.  This signal allows
the ram FSM to know when the read FSM is active.  The ram FSM will
only operate if the start signal is high.  The counter incremented in
the read state is used to count the data samples.  Because of this,
one can tell when the information on frequency samples have all been
sent, and when transmissions of other data have begun.

\subsection{RAM FSM}
The ram FSM controls the preparation of the data to be written into
RAM.  Namely, it controls the organization of the data into groups,
and the control of the external RAM modules.  Because there are only
192 dots wide in the display, and we want to leave room for things
such as axes and labels, we need to somehow convert 1024 samples into
a set of smaller samples to be displayed.  For this, I decided that a
simple time average would be appropriate, as it maintains the general
shape of the signal better than a straight downsample (i.e. taking
every nth sample).
 
Accounting for a one column (one column is 8 pixels) margin on the
right and a two column margin on the left, this leaves 192 - (3 * 8) =
168 pixels for the display of 1024 samples.  1024 divides into 168 six
times, with a remainder of 16, so that means that 6 * 168 = 1008
samples will be used, but 16 samples will be ignored.  We decided that
the last 16 samples should be ignored for a number of reasons.  The
first is that in the higher frequencies, octaves are spaced farther
apart, so 16 samples at the upper end have a shorter range than 16
samples at the lower end of the frequency spectrum.  Another choice
would have been to toss out 16 equally spaced samples, but again,
tossing out the higher frequency samples results in the loss of
slightly less audible information.  Performing the division in
hardware is a potential problem which we will soon solve.

In addition to the width of the display, the height of the display is
also a constraint.  The full height of the visible screen is 192
pixels, divided up into two halves of 96.  Accounting for a one row
margin above and below each half (one row is 12 pixels), this leaves
96 – (2 * 12) = 72 pixels for the display of frequency information.
Since we are transmitting 8-bit real and imaginary components of
frequency data, the largest numerical value of the magnitude of the
frequency data is on the order of 17 bits (we square two 8-bit numbers
and add them), or 2 * (256 * 256) = 131072.  But we needed to keep
track of the sum of six of these numbers together, since we were
taking a time average, so we end up storing a 20-bit number.  We still
need to divide by six, but this can be solved concurrently with the
problem of scaling.  Noting that 72 = 9 * 8, we see that we must scale
a power of two down to a number that is the product of a power of 2
and a power of 3.  We can divide out the factors of two by bit
masking, but the scaling of the factors of three could pose a problem.
In order to be able to divide a 20 bit number down to 72 discrete
values using bit masking, we need to multiply the factor of 9 to the
20 bit number.  However, we still need to divide by six, so we can
multiply by another factor of two, thus canceling out all divisions.
The multiply by 9 becomes a multiply by 18, but when combined with the
divide by six, reduces to a multiply by three.  We can optimize
further by simply adding a bit shifted version of the now 22-bit
number, so a complete 22-bit multiplier is not synthesized.  The
appropriate number of lower bits can now be masked, scaling the 22 bit
number to 72 and averaging the six samples at the same time.  Shown
below in Figure~\ref{numbers} is a flow diagram showing the operations
performed on the frequency samples.
 
\begin{figure}[pbht]
\begin{center}
\includegraphics[width=6in]{numbers}
\end{center}
\caption{Operations converting 2 7-bit numbers into one 7-bit scaled value}
\label{numbers}
\end{figure}


\begin{figure}[pbht]
\begin{center}
\includegraphics[width=6in]{ramfsmdiagram}
\end{center}
\caption{The state diagram for the RAM FSM}
\label{ramfsmdiagram}
\end{figure}
Because of the need to keep track of multiple counters, the ram FSM
has a complicated state diagram, shown in Figure~\ref{ramfsmdiagram}.
The ram FSM starts in an idle state, and is brought into a wait state
when the start signal from the read FSM goes high.  In the wait state,
the ram FSM waits for the complexgood signal to go high, which
indicates that the read FSM has completed calculating a frequency
sample.  The ram FSM then enters the read1 state where it adds the
sample to an accumulator, records the current id of the sending party
in the wholatch, and increments the averagecount counter.  The FSM
then goes into the read2 state where it checks if averagecount equals
six.  If it does, the FSM continues on to the downsample state.
Otherwise, it will wait for copmlexgood to go low before returning to
the wait state, where it will wait for complexgood to go high once
more.  In the downsample state, the value in the accumulator, which is
now holding the sum of six frequency samples, is multiplied by three.
The FSM then goes into the Writebuffer1 state, where it sets the data
output value and address of the information to be written to the
dual-port RAM.  Every new sample --- the average of six frequency
samples --- is written to the dual-port RAM, and when eight samples have
been written, the write FSM translates this into data to be written to
the video memory.  The dual-port memory is organized into two pages of
8 8-bit locations each.  The MSB of the address signifies which page
is to be used, while the three LSBs correspond to the order of the
stored samples (000 being the oldest and 111 being the most recent).
The page signal stores the page number while the sample signal stores
the id number of the current sample.

After writing to the buffer, the ram FSM enters the runstate, where it
sets run high if samples = 7 (note that when samples equals 7, eight
samples have been written to the buffer).  The run signal tells the
write FSM that an entire page of the buffer has been written to and is
ready for final processing.  The FSM will also toggle the value of the
page signal so the other page in the buffer is written to when the FSM
collects the next samples.  In addition, if the columns signal also
equals 22, then the FSM is done receiving visible samples so it
proceeds to the skipstates.  In the skipstates, the next 16 new
samples are ignored.  By the time skipstate2 transitions to the idle
state, 2048 pieces of data should have been sent across the parallel
bus and received.  If samples = 7 but columns $<$ 22, then the columns
signal is incremented, the samples signal is reset to 0, the page
signal is toggled, and the FSM transitions to the read1 state, where
it is ready to start writing data for the next set of 8 samples.
Finally, if samples $<$ 7, then samples is incremented and the FSM
returns to the read1 state, ready to read the next sample from the
read FSM.

On the screen, we are writing from columns 2 to 22, for a total of 21
columns.  Every time 8 samples are written, that is enough data for an
entire column to be displayed.  So, when the column counter hits 22
and the sample counter hits 7, we know we have read in all the useful
information for all the columns and will ignore the next 16 samples.

\subsection{Write FSM}
The write FSM is responsible for writing the correct values to the
proper memory locations so the video displays correctly.  Its main
job consists of translating the eight samples stored in the buffer
into data representing an entire column.  Data for the video is
written eight bits at a time, which corresponds to one of the rows
within an 8x12 block.  Each column of pixels is represented by
a value in the buffer (the value in the buffer is the scaled magnitude
of the frequency samples, which translates into the number of pixels
that should be lit).

The data is converted by starting a counter at 72 and decrementing it,
checking each column to see if it should be lit.  The values stored in
the buffer will be a number between 0 and 72, which designates how
many pixels starting from 0 should be lit.  The counter represents the
highest column and also the lowest memory location.  If the value in
memory is greater than or equal to the counter value, then that means
the pixel in that column should be lit; if it is less than the counter
value, the pixel in that column should not be lit.  The lower three
address bits of the memory buffer correspond to the eight columns in
the 8x12 character cell.  Figure~\ref{blocks} below shows how the read
data corresponds to video data.

\begin{figure}[bhpt]
\begin{center}
\includegraphics[width=6in]{blocks}
\caption{The conversion of ``vertical data'' (frequency sample magnitude) to ``horizontal data'' (video RAM memory values)}
\label{blocks}
\end{center}
\end{figure}


\begin{figure}[bhpt]
\begin{center}
\includegraphics[width=6in]{writefsmdiagram}
\caption{Write FSM state diagram}
\label{writefsmdiagram}
\end{center}
\end{figure}

Figure~\ref{writefsmdiagram} above shows the state diagram of the
write FSM.  It starts in an idle mode and is activated when the run
signal from the ram FSM goes high.  At this time, there are three
pieces of information sent from the ram FSM to the write FSM: the who
signal, which contains the id of the data sender, the page signal,
which designates which memory page is to be read from, and the offset
signal, which tells which column should be written to.  The write FSM
then goes to the readin state where it reads through the memory buffer
page specified by the page signal, and stored the values locally.  The
FSM them transitions to the drawgraph1 state where it enters a loop
that runs until count = 0 (counts is initialized to 72).  In the loop,
the count value is compared to the values read from the buffer.  At
each comparison, a one or a zero is generated, which is then stored in
the signal piece, which is then written to video memory.  Count is
then decremented and the loop repeats.  When count finally reaches 0,
the FSM returns to the idle state and waits for the next run signal.

\subsection{8Kx8 Video SRAM}
The two 8kx8 video SRAMs used has access times on the order of tens of
nanoseconds, which we thought was more appropriate than the available
6264, which had access time on the order of hundreds of nanoseconds.
Our clock period being 100 ns, this allowed us to avoid having any
wait states when accessing memory.  The original design of the video
module called for an 8Kx8 dual-port memory module, but unavailability
of the chip prompted us to a rotating-buffer design, where the MC6847
alternates between reading the two memory modules.  One of the
benefits of using two separate memory chips is that the information on
one frame can be held for as long as it takes to write new information
to the other RAM.  One of the drawbacks, however, is that information
that does not change must still be written to the other memory.

The control signals to the memory had to be multiplexed to ensure that
the FPGA had access to control one memory module while the other
memory module was placed in read mode for the MC6847.  The complex
wiring did prove to be an obstacle, but painstaking tedium and proper
clipping and routing of wires overcame it.  To minimize the use of
inverters on the MUX control signal, alternating
MUXes were often cross-wired.  The connection of the memory I/O ports
to the FSM also had to be tristate buffered, since the memory is
driving the data lines in read mode and the FSM is driving the data
lines in write mode.

\subsection{Dual-port memory buffer}
The dual-port memory buffer has 16 8-bit locations, and is organized
into 2 pages of 8-locations each.  Each page corresponds to a column
(8 pixel-widths) of data to be displayed in the frequency response
graph.  The dual-port memory was implemented on the FPGA as a latch
array, as the FLEX family of FPGAs does not support true dual-port
memory.  The dual-port module is necessary because the ram FSM needs
full write access to the buffer at all times, while the write FSM
needs full read access to the buffer at all times.  Because the memory
was only 16 locations, the amount of space taken up on the FPGA was
tolerable, when compared to the ease of operation that it brings.

\section{Possible expansion}
Among the many ideas for expansion were a ROM containing the video
information for static portions of the screen, such as x- and y- axis
labels, titles, and other asthetic designs.  The possibility of
switching from a full text to a full graphics mode was also
considered, as this would allow a more detailed view of the spectrum,
and with eye-pleasing graphical effects.  At the height of our
ambition, we considered a visualization system similar to those seen
on the multimedia players on computers.  We did not expect to be able
to implement some of those ideas, but the brainstorming process
produced many creative results that would be interesting to see, to
say the least.


\section{Testing and Debugging}
The testing of the video came in two general phases: the testing of
the analog portion and the testing of the digital portion.  The analog
portion was first tested using a simple test circuit wired to the
MC6847, allowing the switches to manipulate the data shown on the
monitor.  This was largely successful, with only a few minor issues.
Then next step as to test a digitally controlled version of the video
display as well as the ability to switch from reading and writing to
each of the memory modules.  A simple FSM was created that would write
to the screen based on the inputs from switches to the FSM.  The MUX
control line was wired to a switch, which allowed me to manually
initiate a writing cycle, and then flip the switch to view the
contents of the RAM.  This ran into some problems that were largely
the result of wiring.  To resolve any doubts, I completely rewired the
system, being sure not to strip too much insulation, which would risk
shorts, and also being sure to make them the appropriate length.

A final video test was done to ensure that I understood the way the
MC6847 was reading from memory, i.e. which memory locations
corresponded to which 8x12 blocks on the screen.  This test was done
by simply writing in consecutive numbers 1, 2, 3, etc. to consecutive
memory locations and observing the results on the screen.  After this
test, I found out that I had used horizontal addressing in designing
my FSMs instead of what appeared to be vertical addressing.  After
making the change, the display still did not function properly.  I had
to perform the address test once more, and on the second time, I
realized that horizontal addressing was used, but the MSB specified
which side (left or right) that the information would be displayed on.

To test the FSMs, Max+Plus II software simulations were used.  Below
in Figure~\ref{top}, the results of a top level 1.23 ms simulation can
be seen.

\begin{figure}[bhpt]
\begin{center}
\includegraphics[width=6in]{top}
\caption{Top level simulation results.  Notice how the values on "column" increase smoothly up until the skipping period.}
\label{top}
\end{center}
\end{figure}


The most frustrating portion of the debugging came from the need to
explicitly state the values of every variable in every state.
Oftentimes, when left unspecified, the compiler will decide to give
the variable any value it pleases.  Because of this, I had to create
many latched signals, highly noticeable by the "x0 $<$= x" format that
they take in my code.  One of the more challenging portions of
debugging was the attempt to circumvent the use of the division
operator.  Oftentimes it took some time to verify that the simulation
was outputting the correct value, because the correct values were
often confusing.

Of course by far the hardest part to debug was the communications
subsection.  Because of its nature, it was impossible to test without
wiring all three kits together.  And even so, it was hard to tell
which kits were causing any problems.  Extensive use of the logic
analyzer allowed us to make some modifications to improve the
communications interface, but it remains in a barely functional state
before decaying into an invalid state, where two kits try to transmit
data to each other at the same time.  Of course spent much time pondering over
these problems, as our simulations often looked fine, as in
Figure~\ref{readfsm2} below.  Also below are other simulation results
of the other FSMs.

\begin{figure}[hp]
\begin{center}
\includegraphics[width=6in]{readfsm2}
\caption{Simulation results of the read FSM.  It acknowledges ready signals and reads the data properly.}
\label{readfsm2}
\end{center}
\end{figure}

\begin{figure}[hp]
\begin{center}
\includegraphics[width=6in]{writefsm}
\caption{Simulation results of a full cycle of the write FSM.}
\label{writefsm}
\end{center}
\end{figure}

\begin{figure}[hp]
\begin{center}
\includegraphics[width=6in]{writefsm2}
\caption{Simulation results of the write FSM during a single read/write cycle.  See how the FSM reads the 8 samples in first and then goes through a series of writes.}
\label{writefsm2}
\end{center}
\end{figure}

\begin{figure}[hp]
\begin{center}
\includegraphics[width=6in]{ramfsm1}
\caption{Simulation results of the ram FSM during a single loop.  The accumulation and counting of the complexin input is shown in glitch-free operation.}
\label{ramfsm1}
\end{center}
\end{figure}

\begin{figure}[hp]
\begin{center}
\includegraphics[width=6in]{ramfsm2}
\caption{Simulation results of the ram FSM performing a write to the dual-port buffer.  The aeragecount counter is reset after the signal is written.  Note the address lines are stable during the write operation.}
\label{ramfsm2}
\end{center}
\end{figure}

\newpage

\section{Conclusion}
Despite the trouble we had in implementing our design, we are certain
that we could have been more successful had we more time to hammer out
the communication problems, more I/O pins, and a more powerful
(larger) FPGA.  The mere undertaking of a project of this scale and
difficult has taught us many things about digital design, so I think
we can measure a good deal of success from our work.  Probably the
most important thing about this last project was the exposure to more
analog components, since in the end, the real world is analog.  In
particular, the topics of motors, transmission and communications, and
video and audio signals are sure to be encountered in the real world,
and will help us greatly in the future.

The many hours of coding, simulation, wiring, and debugging software
and hardware have certainly left their mark on me and my groupmates,
and I suspect that it will last for some time as a testament to our
endurance.  I am willing to admit that I had a good deal of fun in
this great learning process of digital design, and in the end, the
rewards for all the hard work invested could not be sweeter.  Thus
ends a long and hard year in the shadowy and frighteningly real world
of hardware.

%\appendix




