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 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 speciﬁcations.
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 oﬀset into the current microprogram) is also part of the conditions. Conditions may be don't-care values. Conditions are grouped in multi-bit ﬁelds for convenience or to deﬁne 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 ﬁeld, a value from the opcode ﬁeld 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 ﬁelds if they are semantically similar, mutually-exclusive, or if they represent a multi-bit ﬁeld such as a register number.
This, of course, is theory. The assembler may generate microcode that is used in vastly diﬀerent ways. The architecture determines how the bits produced by
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 eﬀects, so
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 oﬀer.
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 ﬁle:
cond OP:4; cond uaddr:3;
signal /MEMIO = ....1; signal MREAD = ...1.; signal MWRITE = ..1..;
ﬁeld 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 ﬁle.
C multi-line comments
/* ... */ and C++ single-line comments
// ... are both understood.
All declarations and microinstructions must end in a semicolon.
This example deﬁnes two condition ﬁelds in the ROM's address: the
OP ﬁeld, four bits wide, and the
uaddr ﬁeld, which is three bits wide. This ﬁeld 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
uaddr. The last
cond deﬁned is the least signiﬁcant part of the address.
signal declarations deﬁne the output of the ROM. The bitﬁeld for each signal is speciﬁed in binary. You may use the traditional values 0 and 1. For increased readability, you may also use the characters
- 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 ﬁelds may be named for convenience. Macros to extract ﬁeld values will be added to any C output generated (in future versions, more functionality will be attached to them). Field deﬁnitions may use
+ to specify a bit that's included in the ﬁeld, and
- to specify bits that are excluded from the ﬁeld.
Microprograms are declared with the
start keyword, followed by a comma-separated set of conditionals and their values in the format
cond=value. If a particular ﬁeld is immaterial to a
microprogram, a don't care value may be speciﬁed using the standard electronics don't care symbol,
start keyword and condition speciﬁcation, the microprogram follows. Each line of the microprogram deﬁnes a comma separated list of zero or more signal names as deﬁned 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 preﬁxed with a minus. For example, the
-MREAD speciﬁcation on line 3 of the microprogram above turns oﬀ the
Multiple microprograms may be speciﬁed with multiple
start keywords. When using don't-care values, start with the least speciﬁc (most
X bits) microprograms and move on to the most speciﬁc 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,
mcasm may not suit you.