The source and/or object code (subsequently referred to as code)
presented in this and subsequent lessons is the property of
saxelec.com and owner who retains copyright protection. This code in
whole or in part is licensed only for single copy per user,
non-commercial use; except in the case of educational institutions,
where a license of single copy per student, only as part of course
curricula, is permitted.
Donald Gerard Polak (owner)
The sequencer block consists of a microcode state machine that
coordinates the actions of all of the other microprocessor blocks, an
instruction latch which retains the fetched instruction until its
execution is complete, a flag mask decoder that generates a distinct
flag mask for each instruction, and an expander that creates a large
number of control signals based upon the microcode state and other
conditions. The instruction latch and flag mask decoder were addressed
in lesson 4. The state machine and expander, however, are closely
coupled. Therefore, they must be discussed as a single unit.
When the microcode state machine begins its sequence it must examine a
variety of microprocessor conditions that may exist at that moment and
decide the appropriate course of action. Therefore, the state machine
must employ priority
encoders in its earliest states to avoid delays.
encoder is a logical construct which assigns levels of importance
to various stimuli. The priority encoder then generates an output
appropriate to the condition that must be "recognized"
first. Priority encoders are logic intensive and can easily be
unintentionally encoded, so most compilers will generate a warning,
and some an error, when one is encountered.
Priority encoders are intentionally or unintentionally encoded in a
variety of ways, however the use of the 'else if' statement is the
Sequential logic priority encoder using the 'else if'
if <condition> begin
else if <condition> begin
Corrected version not using priority encoding:
if <condition> begin
if <condition> begin
Microcode State Machine Organization
The microcode state machine opens with a large number of definitions
to allow states to be referenced by name rather than trying to
remember large bit patterns. These defines also make it feasible to
quickly alter code execution.
The microcode state machine uses common states, that is a state that
is shared by all instructions or groups of instructions, and specific
states that are used by one instruction only. The microcode state
machine begins in its idle state, where it must examine the halt
register and the interrupts before beginning to fetch instructions.
The instruction fetch also employs common states, as the microcode
state machine must request an instruction fetch cycle from the bus
state machine, ascertain that the request is being processed,
increment the program counter, and enable the instruction latch.
It directs these functions via the state bits in the coded fields on
each clock cycle.
Once the instruction is fetched, the microcode state machine can
either use specific states, or more common states, based upon the
instruction. The additional common states will be followed by specific
states, and some specific states can be followed by more common
states. After the instruction fetch, all instructions require the
examination of the contents of the instruction register in order to
determine the next state transition.
Getting Started - Instruction Fetch
Let's begin coding our microcode state machine and add the
with Preliminary Microcode State Machine and Instruction Fetch
The "default" case allows any unintended or uncoded states
to terminate the current instruction and reset the state machine. We
will see later how we use it to force uncoded instructions to perform
The interrupts are currently defaulted to the idle state to allow us
to compile the code. The 'intp' input must not be set to a one until
the interrupt block is coded, or the microcode could become locked in
an idle state.
You will notice that the microcode states are defined as sets of bit
fields. This format allows you to easily encode and understand the
states. Also remember that the state bit pattern is asserted
immediately on the clock edge that satisfies the requirements of the
transition. Therefore, the conditions to increment the program counter
are setup during the clock edge of 'IFETCH2' and the increment
actually occurs or the first clock edge of 'IFETCH3'.
Even though Lesson 2 Module 4 refers to
this method as a particularly dangerous thing to do, we rely on a
combined fields approach to minimize the state machine width. Since
the 'mem' destination always uses only the 8 least significant bits,
we can use 'ude' and 'lde' to virtually expand the width of the
destination field when we are using a 'mem' destination. In this case,
the use of both 'ude' and 'lde' with the 'mem' destination allows us
to enable the instruction latch. We will also use similar methods, in
the future to allow field expansions without increasing the width of
the state machine. These combined field approaches must be carefully
researched in advance, as explained in Lesson
2 Module 4, but they may also help us to reduce the number of
required differentiator bits. Reducing the state machine width will
significantly improve speed and reduce logic requirements.
A Few Simple Instructions
As stated previously, in order to execute the instructions, we must
decode the contents of the instruction latch. We chose a
transparent latch for the instruction
latch to allow us to begin decoding the instruction as soon as the
instruction data is stable and valid, in our case this occurs in
microcode state 'IFETCH3' when 'dack' is asserted.
Initial instruction decode begins by using a 'case' statement acting
upon the contents of the instruction latch. This case statement allows
you to execute all instructions, but for now we will concentrate on
single cycle instructions only. Multi-cycle instructions will be
addressed in later lessons. Don't worry, Verilog allows the
nesting of 'case'
and 'if' statements
So let's try adding some simple, single-cycle instructions to our
with Preliminary Microcode State Machine and Some Simple
You will notice that the instructions, so far, are decoded and acted
upon in the IFETCH3 state. Actually all instructions must be decoded,
and a state transition occur for that instruction, during IFETCH3;
which (if our architecture is correct) should correspond to the
rising 'edge of rd_n (from the bus state machine perspective). This
leaves us one and one half sysclk cycles before the next bus cycle
should occur. One half sysclk cycle will be consumed by our next bus
Timing between bus and microcode state machines.
Since the bus state machine is setup to handle 'early' request
postings, the microcode state machine does not have to worry about
'sysclk' timing. After instruction completion, the next bus request is
posted from the 'MIDLE' state, which is entered via the 'default'
microcode case state for now.
Each instruction will require a state transition after 'dack' is
recognized in microcode state 'IFETCH3'. Some of these state
transitions will be common states, while others will be specific
states. You can notice, for example, that all "No Operation"
instructions vector immediately to the 'MIDLE' microcode state; while
the operational instructions (thus far) have an associated specific
state. These specific states are then transitioned to the
"MIDLE" state on the next clock cycle by the default
statement at the end of the microcode case statement.
To code an instruction in 'IFETCH3' you must first determine an
existing, or create a new 'parameter' statement to setup a microcode
state for that instruction; which, in turn, sets up the necessary
logic to perform that step of the instruction execution. Then you must
create an 'instruction' case statement for that instruction within the
'IFETCH3' microcode state which invokes that microcode state.
You may notice that a number of single cycle instructions were left
out of the instructions that I coded or left for you to code. The SIM,
RIM, IE, DI, 'DAA', and 'XCHG' instructions do not yet have all the
architecture in place to support them, and thus must be addressed in
By now you've probably realized that it would be useful to keep track
of all the coded and uncoded instructions. I generally favor importing
the contents of the parameter file into a word processor, such as
'WORDPAD', trimming all except the instruction codes parameters, and
then using the 'FONT' format 'STRIKEOUT' characteristic for the
instructions I've already completely coded. Since I wanted to include
instruction cycles and flags, I created a table in FrameBuilder and
colored in the coded instructions.
Project Tracking with Coded
Instructions Colored in
A piece of paper works good too.
The microcode state machine is not very useful until the states that
it generates can be translated into the various control signals
required by the other modules. Since there are many more control
signals required by the modules then there are state bits out of the
micro-code state machine, the expander de-multiplexes the various
fields of the current microcode state into these control signals.
This statement automatically implies two things:
The expander block requires the same field definition parameters
as the microcode block. According to
Rule 6.6 at least some of the
parameters from "microcode_r" need to be moved into the
The expander works best upon fields. This implies that the
microcode state presented to the expander must either:
Be conveyed as a set of fields.
Have a set of "`define" statements to delineate
the various fields. These "`define" statements
consist of text replacements such as:
`define CREQF 23
assign creq = microcode[`CREQF];
I personally favor the former approach, since changes in the microcode
structure will only affect the microcode state machine itself, while
using the definition style would cause both the microcode state
machine and the expander modules to be affected. Also, since
"`define" statements are global in scope, there is potential
conflict with other modules. Generally, it is best practice to use
"`define" statements only for items that are global in
nature or that affect several modules; not for interaction between two
When I chose the parameters for the various fields in the
microcode state machine, I did so with minimizing the number
of inputs required for each expander output signal in mind.
The operational states that require the use of source
complement, with the exceptions of DECOP and DECNF, are
grouped so that bits 4, 3, and 2 will trigger comse
whenever they are 3'b001.
Fields that can be mapped directly to signals are coded in
such a way as two allow direct mapping, without the use of
The expander does not require the use of the
differentiator bits either, since they only affect the
microcode state machine itself.
For this reason, some of the parameters may change from time
to time during the remainder of this course, as we are
designing this microprocessor together, and I haven't tried
out all of the new code yet.
So let's start our coding by moving our field parameters
into the parameter file:
Parameter file with microcode field parameters added
2. Incorporating Fields
Next we must make changes in microcode_r to remove the
parameters that were just moved into the parameter file, break
the microcode into separate fields, alter the module and
output declarations to reflect the change from outputting
micstate "en-masse" to outputting the separate
fields. We must also alter sequencer_r to incorporate these
changes in the interconnect between microcode_r and
The expander can now be encoded. The expander is essentially
a sea of non-sequential logic. Each output from the expander
must be coded in the most efficient manner possible. One
technique uses the field parameters directly and lets the
compiler decide the most efficient coding. Another technique
involves writing the actual logic equation for the output.
Wherever possible, direct mapping is used. During the
development of the expander, we will periodically modify the
field parameters for the microcode to optimize this block.
Compiling and De-bugging
Finally, we will make all corrections in any other modules that are
required to get the full design to compile.
1. Synthesis Corrections
To make the corrections just mentioned, start with the errors.
The errors are usually the result of typo's and are generally
easily corrected. The next step is harder. You must examine
the warnings. On a new project such as this, they can number
in the hundreds (about 900 on my first synthesis). The windows
presentation of the synthesis log may prove difficult to use
at this stage. For most tools, the synthesis log file is a
text file and can be opened with most text editors. I prefer
to use jedit, because it allows me to open the source file and
the synthesis log simultaneously. Focus on the warnings that
mention 'unassigned' values and the ones that result in logic
removal. These occur frequently because of undeclared signals
or connections. A single correction at this stage can
result in several (or sometimes hundreds) of warning removals.
Periodically re-synthesize your design while making these
corrections to determine your next step. In the end, you may
still be left with a significant number of warnings that are
the result of un-coded blocks, but it should be less than 100.\
2. Compile Preparations
To allow the code not only to synthesize, but to also compile
we must remove any occurrences of "black boxes". A
black box only sinks signals and does not source any. We use
them early on in the development cycle as place holders for
later logic. The only black box we are currently left with in
this project is "interrupt_r". Since we know that
signals such as "intp", "xint", and
"mask" must originate from "interrupt_r", we
can eliminate its black box status by assigning those signals to
default values within that module and connecting them to the
You must also do some settings in your compiler. Since the
expander uses the fields from the microcode state machine
directly, you must disable the options that allow the compiler
to change that encoding, such as "FSM expansion",
FSM optimization", "Gray code encoding", etc.
Congratulations. We have now reached a point where we have a 16MHz
calculator that can add, subtract, increment, decrement, and perform
some basic logic instructions; but do very little else. However, we have
most of the guts in place to do a whole lot more.
First Executable Project Skeleton
I want to call your attention to a particular coding technique used in
"expander_r" for "adde". As I explained in
Rule 3.9 we must plan for a 4
input single output style logic cell. This constraint may seem very
limiting at first, until you start employing this technique. In two
logic levels you can theoretically achieve a single output that is the
result of sixteen inputs. However, it seldom works so cleanly. Some
terms, tend to be dominate, that are they are combined with an
assortment of other terms, while others are subordinate. Therefore,
the achievable function input levels trend downward somewhat. But
actual fanins for two logic levels can generally reach 8 - 12. In our
case, the fanin is only five.
Now that we have enough infrastructure in place, we can start
simulating our project. However, we must update our test bench first.
First of all, we will need some sort of ROM to supply the instructions
during instruction fetch. We will also need some sort of address
decoding, so that addresses will select the proper memory device,
according to the memory map. In our project, I setup a memory map that
references the ROM during the first 2K of memory addresses and RAM for
any other memory address. We don't need the RAM just yet however.
In the real world, ROM is fairly slow, so I implement a wait state
during ROM access by setting up a delay line that sets ready low for a
single sysclk cycle.
Finally, we need to change the source select mux in the test bench to
drive the ROM output onto the AD bus during read.
I wrote a few instructions into the ROM for demonstration purposes. I
did not code the entire ROM, but if you're energetic feel free to do
so. With that being said, here is my new test bench:
I simulated our project with IVERILOG. First I created a directory for
simulation. I then copied the source files into that directory and
renamed them from the command prompt:
ren s8085d_skel2_r.v.txt s8085d_r.v
ren s8085d_t_pre2.v.txt s8085d_t.v
ren s8085d_p_pre4.v.txt s8085d_p.v
ren s8085d_t_d.v.txt s8085d_t_d.v
I added the IVERILOG binary path to my system environment PATH
variable. I then launched the COMMAND PROMPT and changed my directory
to my simulation directory. From there I entered the following
iverilog -o s8085d_t.out s8085d_t.v
vvp s8085d_t.out -lxt
When vvp encounters the $stop directive it does not automatically
exit. Instead, it will present a caret ('>') prompt and await
further commands. I enter 'finish' at this prompt to exit vvp.
We can now use GTKWAVE to view the waveform. I entered the following
From the gtkwave window, I added the clock, and most external bus
signals to my display so you can view the actual instruction bus
cycle. I also added most registers and the instruction latch so you
can see the results of the instructions:
GTKWAVE Display of Instruction Fetch
GTKWAVE Display of Instruction Results
What is a "Priority Encoder"?
Do you have to intentionally encode a "Priority Encoder"?
Why did we need a transparent
latch for the instruction latch?
What must happen after "dack" is asserted?
Encode the "left for you" instructions in microcode_r.
What steps did you have to perform? Where did you put them?
Why did we break the microcode into fields?
Describe the microcode expander.
How many inputs are theoretically possible with a two level logic
What items should you focus on to get your project to compile