This is a simple microcode assembler (or microassembler). It can be used to generate wide, horizontal microcode ROMs for relatively simple CPU architectures. It's mainly intended for hobbyists working on home-designed CPUs. Using a description of the microcode, it generates one or more ROM, EPROM, EEPROM, Flash et cetera images suitable for inclusion in a software emulator, a hardware device, or for use in a hardware description language such as Verilog or VHDL.
Microcode in Theory
Microcode is a collection of microprograms. Each microprogram depends on a number of conditions and is made up of a number of microinstructions. Each microinstruction is in turn made up of a number of signal specifications.
Microprograms implement basic functions. In most cases, each microprogram will implement one machine instruction. This, of course, depends on the machine architecture.
Conditions are inputs to the computer's sequencer. They include major states of the machine such as whether an interrupt is being serviced, whether the machine is being reset, et cetera. A microprogram counter (the offset into the current microprogram) is also part of the conditions. Conditions may be don't-care values. Conditions are grouped in multi-bit fields for convenience or to define multi-bit values like opcodes. Microprograms are selected and executed when the conditions match. The matching itself is down to the hardware. For instance, to match an opcode field, a value from the opcode field of the instruction register would be fed to the corresponding address lines in a microcode ROM.
Microinstructions perform tasks simultaneously by directly manipulating the sequencer by (you guessed it), signals. Signals may be asserted or de-asserted. Some signals are active-high, others are active-low. Signals may be grouped together into fields if they are semantically similar, mutually-exclusive, or if they represent a multi-bit field such as a register number.
This, of course, is theory. The assembler may generate microcode that is used
in vastly different ways. The architecture determines how the bits produced
mcasm are interpreted in practice.
A lot of microcode writing can be repetitive, and repeating structures by hand
makes things prone to errors. Errors in microcode have potentially disastrous
cpp, the C Preprocessor to
prepare input for it. That makes the assembler syntax fairly C-like in some
respects. As a bonus, you get parametric macros, conditional compilation, and
all the other goodies
cpp has to offer.
The syntax of
cpp is beyond the scope of this article. Please
refer to its documentation.
Here is a very brief example of a microcode input file:
// Minimum Microcode file. cond OP:4; cond uaddr:3; signal /MEMIO = ....1; signal MREAD = ...1.; signal MWRITE = ..1..; field REGMOVE = XX___; signal MAR <- PC = 01...; signal IR <- MEM = 10...; start OP=XXXX; // A tiny fetch instruction /MEMIO, MREAD, MAR <- PC; // hold; // Same as MEMIO, MREAD, MAR <- PC hold, -MREAD, IR <- MEM; // Same as MEMIO, MAR <- PC, IR <- MEM // End of file.
C multi-line comments
/* ... */ and C++ single-line
// ... are both understood.
All declarations and microinstructions must end in a semicolon.
This example defines two condition fields in the ROM's address:
OP field, four bits wide, and the
which is three bits wide. This field is the microprogram counter used to
address the currently executing instruction. It is mandatory to include it, but
its width is completely arbitrary.
mcasm will warn you if any of
your microprograms are too big for the chosen size of
cond defined is the least significant part of the address.
signal declarations define the output of the ROM. The bitfield
for each signal is specified in binary. You may use the traditional values 0
and 1. For increased readability, you may also use the
- for zero.
Conditionals are made up of letters, numbers, and non-whitespace characters
except colons (
:), semicolons (
;) and equal signs
Signal names may contain any character except the equals sign
=). Notably, they may include spaces (for legibility). A signal
may not start with a minus sign (
Signals starting with a slash (
/) are recognised as active-low
signals. The corresponding bits are inverted when the ROM bitmap is written.
Signal fields may be named for convenience. Macros to extract field values will
be added to any C output generated (in future versions, more functionality will
be attached to them). Field definitions may use
+ to specify a bit that's
included in the field, and
specify bits that are excluded from the field.
Microprograms are declared with the
start keyword, followed by a
comma-separated set of conditionals and their values in the
cond=value. If a particular field is immaterial to a
microprogram, a don't care value may be specified using the standard
electronics don't care symbol,
start keyword and condition specification, the
microprogram follows. Each line of the microprogram defines a comma separated
list of zero or more signal names as defined previously. The order of signals
in each line does not matter. Lines are terminated with semicolons (so that, in
practice, each may span multiple actual lines in the source code).
A special keyword used in some cases is
hold. When encountered, it
indicates that the set of signals active on the previous line should be active
on the line where
hold appears. In this case, some signals may be
deactivated by listing them prefixed with a minus. For example,
-MREAD specification on line 3 of the microprogram above turns
Multiple microprograms may be specified with multiple
keywords. When using don't-care values, start with the least specific
X bits) microprograms and move on to the most specific ones.
mcasm can generate microcode for architectures where microcode
jumps around a lot, but its syntax is more suited to architectures where
microcode is executed mostly sequentially with occasional jumps. If your
architecture doesn't even have the notion of a microprogram,
may not suit you.