little bat k1.spdns.de / Develop / Hardware / K1-Computer / Software / Microcode /
Overview | Microcode | Assembler | Header Example | Materialarchiv

The K1-16/16 Microcode and Microcode Assembler

Last updated: 2010-08-16

Overview

The control unit (abbreviated "SSW" for German "Schrittschaltwerk") consists of clock generation, counter cascade, microcode eproms and rams and decoders, which spread binary coded bitfields into mutual exclusive control signals for the other components of the cpu.

The microcode eproms are copied into the rams after reset. The eproms could be replaced by some other source for the code to get a Harvard-style cpu. The high-speed cmos rams allow higher clock speed and were obtained from scrapped '486 PCs on ebay. The code is stored in three 27256 eproms and three 24257 rams which provide 32k words of 24 bits.

The control unit can read a microcode address to jump to from the data bus – this address can be thought of as an assembler opcode. I will refer to them as assembler opcode in this document to avoid confuscating them with microcodes. And the control unit can put 16 bit data on the data bus, e.g. to store it into some other data bus unit or to read it back itself, thus performing a jump in microcode.

The control unit is connected to all other boards via 4 groups of connectors:

CON1: 16 bit data bus + 16 bit address bus (the address bus is not used by the control unit)
CON2: 16 output control lines for data bus units; abbreviated 'oe' ("output enable")
16 input control lines abbreviated 'clk' ("load clock")
CON3: 8 output control lines for address bus units; abbreviated 'oe' ("output enable")
8 input control lines abbreviated 'clk' ("load clock")
CON4: 6 general purpose option lines shared by all units + 6 feedback lines, the flags.

Microcode

General Layout of Microcode Instructions

The microcode is organized in 2 planes of 16k of 24 bit microcode instructions.
Each microcode contains 3 bits to select a condition, which was tested in the previous code to determine the plane from which the next microcode is read.
Each microcode contains a bit 'cmden' which indicates whether it contains a microcode instruction or immediate data.

If the cmden bit in a microcode instruction is cleared, then the instruction consists of:

• 3 bit condition select
• 1 bit 0 = cmden
• 6 bit option lines
• 3 bit address bus target
• 3 bit address bus source
• 4 bit data bus target
• 4 bit data bus source

If the cmden bit in a microcode instruction is set, then it is named 'cmddis' and then the instruction consists of:

• 3 bit condition select
• 1 bit 1 = cmddis
• 3 bit address for DIV.FF
• 1 bit data for DIV.FF
• 16 bit immediate data

Special Cases when Loading New Microcode Addresses

Loading addresses into the control unit has some peculiarities:

• Data bus bit D15 is ignored. The microcode roms and rams are 32k only. But there is one condition code which tests data bus bit D15 which can be used to select the "code plane" for the jump destination from data bus bit D15.
• D14 determines, whether the low byte of the new microcode address is actually loaded or cleared.
  This allows microcode jumps and thereby assembler opcodes which start at addresses $xy00 to incorporate 1 byte immediate data in their low byte. This byte is actually not stored in the control unit, it must be loaded by a data bus register, which is clocked by the same 'clk' line which loads a new microcode address from the data bus into the control unit.
  • D14 = 0 --> low byte is cleared
  • D14 = 1 --> low byte is loaded
• Microcode address MA14 is the "code plane" and is set from the condition in the previous codes.
• If a new assembler opcode is read from ram while an interrupt is pending, then the lower 12 bits of the new microcode address are cleared; that is, it is replaced by the interrupt vector. Thus loading a new assembler opcode results into a jump to 8 possible entry points for the interrupt. Address $0000 in plane 0 must be avoided, because this is the reset entry, and for this reason assembler opcodes cannot start in range $0000 to $0FFF in plane 0.

Latency, Conditional Execution and Branching

The microcode is organized in 2 planes of 16k microcodes. The 3 highest bits select one of 8 conditions, which was tested in the previous microcode and deliver microcode address MA14 when reading the next microcode from the microcode ram. 2 of these conditions simply select '0' or '1' resp., which are used to branch into these planes or to keep the flow of microcode in the respective plane. The other 6 conditions select one of the 6 feedback lines, the 'flags' from connector CON4.

The flags are currently dedicated as follows:

0 = 0     force plane 0
1 = 1 force plane 1
2 = cy unsigned carry from ALU
3 = z zero condition from ALU
4 = ovfl signed overflow from ALU
5 = rnd random bit from the RNG on the i/o board
6 = bit0 data bus bit D0; used for shifting
7 = bit15 data bus bit D15; shifting and number sign

The dedication can be changed to reflect modificated designs of ALU or other boards. Flags are not stored in the control unit, they are present only at the moment when they occur, for one clock cycle only. The microcode will have to be forked at that moment immediately, whereafter the currently executed plane reflects the flag's state.

The control unit can put 16 bit data on the data bus and read it back immediately as the new microcode address. D15 is ignored (but may be used indirectly via condition code bit15) and D14 determines, whether the low byte is actually loaded or cleared, as stated above, while microcode address MA14, the code plane, is loaded from the condition set by the previous codes. There is no microcode address MA15, because the microcode is 32k (2 x 16k) in size only.

The funny obstacle, that the code plane MA14 for the next microcode is loaded acc. to the condition selected in this microcode testing the flags in the previous microcode, thus involving 3 different microcodes, results from the 1-step pipeline in the control unit:

With (the rising edge of) each clock cycle, a new microcode address is latched, either calculated by incrementing the old address or by loading it from the data bus.

Simultaneously, the code just read from the microcode rams is clocked into the decoder stage which immediately triggers action.

So, any new address must make it's way from the data bus over 2 clock shoulders before it is performed. The condition code comes from the middle stage, read just in time from the microcode rams to select a feed-back line, which feeds back some outcome of the currently performed code to deliver MA14.

So there are the 'jumping' code, the next code which selects the condition and the next code thereafter which address is loaded from the data bus (address bits MA0-13) together with the outcome of the condition test (MA14).

This is hard to understand (at least for me) but once you have memorized the 'how to' it becomes easier. And actually the most of the headache is now cared for by the microcode assembler.

Example:
    ld  cmd,a0      <-- load address register a0 into the control unit's address counters
  nop : 1 <-- already loaded and therefore executed before jump takes effect
  the jump's destination plane '1' is set from this microcode

"ld cmd,a0" is the previous opcode, during which the condition was tested.
"nop : 1" is the code in the middle, which delivers the condition code. Actually, the condition ":1" does not test anything but always delivers "1".
"(a0)" the code after the code in the middle, is read either from plane 0 or 1, depending of the outcome of the condition test, which was invariant '1' in this example.

When a jump to an unknown address is performed, which may be in plane 0 or plane 1, there is a simple trick to extract the code plane MA14 from the loaded address. This requires the convention, that the value of MA14 is stored in bit 15 of the address, while bit 14 should be typically set to '1' to avoid the 'clear low byte' feature:

    ld  cmd,(sp++)  <-- load address from ram at location (sp++); vulgo: 'return'
  nop : bit15 <-- executed before jump takes effect, chooses the condition 'bit15'
  the dest. plane is read from data bit 15 during the 'ld' code

A jump to a fixed microcode address is performed, if a microcode puts immediate data on the data bus and reads that data back into the control unit's counters. The action of loading the address is performed by a 'executed' microcode with cmden=0, which is followed by a 'data' microcode with cmddis=1 and 16 bit data in the lower 2 bytes of the code. Both codes are present at the same time in the control unit at different stages. After performing the jump, the 'data' code advances to the execution stage where it is not executed, because of cmddis=1. The time for this cycle is lost. But the condition selected in the data code, which is the next microcode after the jumping code, determines the destination plane.

Example:
    jp  my_label : 1

results in 2 microcodes:

    ld  cmd,ival        <-- load data (address) into control unit from control unit (ival)
  dw my_label : 1 <-- dest. address as used by prev. code + condition to select dest. plane

If the destination plane is known, then it can be hard coded in the 'data' code, but it is also possible to use the 'bit15' trick from above.

DIV_FF

The 16 bit data must be put actively on the data bus by the preceeding code.

In contrast to that there is one bit of data which is automatically loaded into the addressable data latch DIV_FF on the control unit, a 74HC259. The 3 bit address and 1 bit data come from bit positions which normally control the option lines opt2 to opt5 on the CON4 connector. This is even true during eprom copying after reset and has provided me with some severe headache. The 8 output lines of the DIV_FF are not exported to one of the connectors and are all consumed on the control unit board itself:

0 = clock2      low  bit of the system clock prescaler: 0=enable (1:2), 1=disable (1:1)
1 = clock4 high bit of the system clock prescaler: 0=enable (1:4), 1=disable (1:1)
2 = halt wait until interrupt
3 = ei enable interrupts: replace opcode with interrupt vector during opcode fetch
4 = green led
5 = yellow led used for status display
6 = red led
7 = reset reset. does not work well currently due to missing clock synchronization

Because loading data into this latch in a cmd_dis'ed microcode cannot be avoided, these 4 bits must be set to a safe value if the 'data' code is used for the 16 bit immediate value. The best choice is to set it to addr 7 (reset) = 0 (don't reset).

Interrupts

When a new assembler opcode is read from ram, it can be replaced with the interrupt service address. If a new opcode is read from ram while an interrupt is pending, then the lower 12 bits of the new microcode address are cleared. For HW simplicity the microcode address bits MA12 and MA13 are not altered and the code plane MA14 cannot be loaded anyway, leaving 2 x 4 possible starting points for interrupts.

This only happens if a new opcode address is loaded from the data bus, and:

• the instruction pointer ip outputs data to the address bus
• the ram outputs data to the data bus
• an interrupt is pending on the /INT bus line (available on CON4)
• and interrupts are enabled in DIV_FF.

Address bits MA12 and MA13 are not cleared because the high 4 bit counter must never be cleared – this would perform a reset – and modifying the supplied data would need at least 1 additional gate in the data path which will reduce the maximum clock frequency up to which the control unit can load opcodes from ram in one clock cycle – thus determining directly the maximum reasonable clock frequency for the cpu.

Bummer ... i did not notice that D14 runs through 3 gates before reaching the 'clear' pins of the counters. so even more time is lost anyway...

Therefore an interrupt will jump to microcode addresses $0000, $1000, $2000 or $3000 in code plane 0 or 1, depending on the condition which is set in the microcode after the one which tried to load the next assembler opcode, which should be condition 'bit15' in all cases.

Thus loading a new assembler opcode results into a jump to 8 possible entry points for the interrupt. Address $0000 in plane 0 must be avoided, because this is the reset entry, and for this reason assembler opcodes in range $0000 to $0FFF in plane 0 are not possible. This leaves only 7 entry points for the interrupt vector, which probably will simply jump to one common handler.

After interrupt processing, the omitted assembler opcode must be restored. This is the reason why the loaded microcode address is only superseeded if it is loaded from ram via instruction pointer 'ip'. Then the instruction pointer can simply be decremented and the opcode fetched again.

The fact that register ip was incremented is not tested but assumed. So restoring the last assembler opcode will fail, if it was loaded without incrementing the ip, which probably makes never any sense and therefore is not allowed.

Reset

After reset the contents of the microcode eproms are copied to the microcode rams. This state is flagged in bit MA15 which is not used to address the microcode eproms/rams, but is labeled INIT. After reset all counters are cleared. While MA15 = INIT is 0, the data is copied. When all 15 bit addresses are counted through, then MA15 = INIT toggles and indicates non-init state and codes are now actually performed.

The init circuitry results in the following conditions after reset:

• Microcode from address $7FFF – that is equal to $3FFF:1 (address $3FFF in code plane 1) – is executed
• Implicitely with cmddis set, regardless what is actually stored in this code, because it was forced to be set by the INIT condition
• Therefore, this code acts as 'data' and the contained address and bit for the DIV_FF are stored.
  The best is, to store a safe NOP into this code (cmddis + reset=0)

• While $3FFF:1 is executed, $0000:0 is loaded and will be executed next. The code plane is 0 regardless of the setting in code $3FFF:1, because it was forced to '0' by INIT.

The Microcode Assembler

The microcode assembler is written in the vip script language and is interpreted by vipsi, the vip script interpreter running on my iMac, which is the server which has just delivered this page to you.

This has two consequences:

1 - You do not have a vip script interpreter installed
2 - I could supply a front-end to the assembler on my iMac on request
3 - You could download vipsi and the microcode assembler script

I will write a cludgy front-end, if someone demands me to do so. Until now, no one has requested it, so i save my time for other work. Else you could install vispi on any un*x based operating system; i have released it under a BSD-style license. Or you convert the assembler source to any other language you like.

General

This is the assembler for the microcode in the control unit. It is used to implement instructions for use by 'von Neumann' assembler programs in the data ram of the CPU. It is powerful enough to write whole programs in microcode as well, if you prefer the 'Harvard' way of life.

The first version of the assembler was a 2-pass assembler. It reads one file of source and generates 3 hex files suitable for any eprom burner, a list file and label listings. It tries to check for conflicting use of option lines and some other error conditions. It handles code flow diverting with some pseudo instructions and allocates code into plane 0 and 1 with a restore-and-retry approach, relieving the programmer (that is: me) from this error-prone task.

The following stuff refers to this first version of the assember, but most of it is still valid for the current version which will be documented when i have more spare time.

Command line options

The assembler sports one source file only (which can include other files) and derives the destination file names from the source file's name. In addition to that, it supports the following command line options:

-v  increase verbosity. may be given twice
-w include warnings into the list file
-h show help

Source File

Comments

Comments can be added at any position in the source, either up to the end of line or blocks of text:

    jp  $           // loop until reset
  ld a0,l103 /* base of table */ + 2 /* offset */
/* now more 8 bit options follow.
all use a0 as base address
*/

Assembler Directives

Assembler directives start with a hash in the first column.

#include "filename"     includes another file
#if <condition> conditional inclusion of source blocks
#elif <condition> ""
#else ""
#endif ""

Labels

Label names must be unique. Label definitions start in column 1 and come in 3 flavours:

Code address labels

These are defined to identify jump targets for jp and call. The colon ':' is optional.

my_label:   <instr> ...
Numeric constants and soft labels

These can be redefined subsequently:

label12 = $+1           // define a soft label
max_int = 8 // constant
label12 = label12 + 10 // redefine soft label
Global variables

Global variables can be enumerated using the 'data' pseudo instruction:

foo:    data 1          // alloc 1 word
bar: data 20 // alloc 20 words
toto: data 1 // etc.

Assembler Opcode Definition

Assembler opcodes, that is, starting points of useful code sequences in the microcode roms, start with a colon in column 1.

Fixed Address

Fixed addresses in both code planes can be assigned to opcodes. They should be defined early in the source before the assembler has used the address range on its own decission. Mostly used for the Reset and Interrupt entry points:

: IRPT0 = $0000:1   // address 0 in plane 1
  jp irpt3 // some code
Aligned Address

Addresses can be aligned to multiples of a selectable granularity. Is is mostly used for assembler opcodes starting at addresses $xy00 which can take an immediate 8 bit argument in their low byte.

: BRA_P % $100  ( -- )      // BRA +dis
  ld ip,ip+dis
  next

The example also shows the supported syntax for arguments – basically the brackets and a comment – which are simply forwarded to the generated opcode listing. The above example generated the following line in the opcodes output file:

$8D00  BSR_P    ( -- )     // BSR +dis

Note, that bit 14 is 0 in this opcode. This will clear the low byte to $00, regardless of what is stored in the assembler opcode later, so this byte can be used to transport a byte-sized immediate value. For all opcodes, which do not start on a multiple of $100, bit 14 is set.

Microcode Instructions

Microcode instructions start in column 2 or later. Most generate code, many even more than 1 microcode. Some control code deposition.

Microcode Instruction Format
label:  instr   dest, src, options : condition

After the optional label, the instruction follows. After that the required arguments, if any. Then any amount of self-defined options can be appended, to tweak the option lines (or whatever). The assembler verifies that no conflicts occure. Finally, after a colon, the condition code for the next instruction follows. If it is omitted, then the assembler generates condition '0' for plane 0 and condition '1' for plane 1, to keep the flow of microcodes in the current code plane.

There are three groups of instructions:

Pseudo instructions, standard instructions and flow control

Pseudo instructions

These instructions generate no code. Quite a lot of them are required to define names, which the assembler internally uses but does not define itself. If i forget one, the assembler complains. See the example header file listing below.

areg dreg div cond opt instr

areg <name> = <nr>

Define names for address bus entities 0 to 7.

The following names must be defined because the assembler uses them internally:

  areg ip   = 0     instruction pointer
areg sp = ... stack pointer
areg atmp = ... address register to use for scratch
areg dxa = ... data to address bus bridge register (oe only)
areg anop = ... dummy target when address is not latched into any register (clk only)

dreg <name> = <nr>

Define names for data bus entities 0 to 15.

The following names must be defined because the assembler uses them internally:

  dreg cmd  = 0     the control unit itself
dreg mem = 1 the data ram
dreg alu = ... arithmetic/logic unit
dreg dnop = ... dummy target when data is not latched into any register (clk only)
dreg axd = ... address to data bus bridge register (oe only)
dreg cmd_lo = ... the byte register which latches the cmd low byte
dreg dtmp = ... data register to use for scratch

cond <name> = <nr>

Define names for conditions 0 to 7.

The following names must be defined because the assembler uses them internally:

  cond 0     = 0    uncond. code plane 0
cond 1 = 1 uncond. code plane 1
cond bit15 = ... test data bus bit d15

div <name> = <nr>

Define names for DIV_FF bits 0 to 7.

There are no required names.
Note: The bits "div reset=0", used in every immediate value (with cmddis=1), are hard-coded in the assembler.

  div ei    = ...   the "enable interrupt" bit

opt <name> = <mask>

Define options and option sets, which may be added to instructions to achieve special effects.

An option name can either name a mask only, which defines which bits are used, or a mask plus value.
If no value is set, then the value must be appended when the option is used later.
An option can also define masks for any other set of bits, e.g. register clk or oe lines.
Masks can be defined using the operator '<<'.
Multiple bit fields can be combined to an option set with operator '+'.

  opt foo = $01000      no value
opt fo1 = $01000:1 value set
opt dis = 3<<10
opt mks = 3<<10:3 + fo1
opt d1o = alu.oe data register output enable mask and bits for data register 'alu'
opt d1c = alu.clk data register load clock mask and bits for data register 'alu'

The following names must be defined because the assembler uses them internally:

  opt a_bits    =   bitmask for option lines used by the address adder
opt a_dis = bitmask for option line which selects use of address displacement
opt add0 = set for add 0 (load as-is) when loading address register from data bus
opt add1 = set for add 1 when loading address register from address register
opt sub1 = set for sub 1 when loading address register from address register
opt adddis = set for add cmd_lo when loading address register from address register
opt subdis = set for sub cmd_lo when loading address register from address register
opt alu_fu = bitmask for option lines which select the function for the alu
opt add = set for alu function "add"
opt sub = set for alu function "sub"
opt load = set for alu function "load"
opt load_cpl = set for alu function "load complement"
opt and = set for alu function "and"
opt or = set for alu function "or"
opt xor = set for alu function "xor"
opt cy = set for alu cy=1
opt nc = set for alu cy=0

instr <name> <argument list>

Define own instructions, or 'macros'.
These can be used later as instructions.

Example instruction which has no arguments:

  instr foo
add 2
end

Example instruction with 2 arguments. Caveat: This performs a stupid search-and-replace, so make shure that the argument names are unique. I use upper case letter for that reason:

  instr mem_get_address_and_size(AREG,DREG)
ld DREG,(AREG)
ld AREG,DREG // AREG -> data.size
ld DREG,(AREG++) // AREG -> data.data; DREG = data.size
end

Labels in macros are not supported.

Incantation is like a standard instruction without brackets:

    mem_get_address_and_size  a0, d1

Standard Microcode Instructions

These are instructions which actually generate code.

set clr nop dw inc dec next ret jp jsr psh pop ld ex
and or xor cpl tst equ add add_c sub sub_nc cmp cmp_nc
Aliasses: incr decr call push

set <name> | clr <name>

Set or clear bit in DIV_FF.

nop

A nop, e.g. to bypass a conditional microcode in the other plane.
The nop generates "cmd_en" with no data sources and destinations and options set. It can be used as base to construct any executed microcode.

dw <value>

Creates an immediate value which is not executed but must be loaded by the previous instruction. May be useful if some code tricks cannot be achieved with the provided instructions.

Example:

    ld  cmd,ival        // this is the naked 'jump' opcode
dw label_foo : z // loads this label address and plane bit from the z flag (weird!)

inc <areg> | dec <areg>

This is a short form for 'ld <areg>,<areg> +1' or 'ld <areg>,<areg> -1'.

Only address registers can be incremented or decremented. Data registers must add/subtract 1 in the ALU.

next

Fetch the next assembler opcode from ram. This performs a microcode jump to the loaded address.

Most times results in the two codes:

    ld cmd,(ip++)
nop : bit15

In addition, the assembler tries to postpone the last microcode before 'next' into the trailing 'nop'. There are many obstacles but in many cases this is possible. This is better than doing it by hand, because (hopefully) the assembler makes less errors and 'sees' which microcode is actually the directly preceeding code, which may be sometimes hard to know, because many microcode instructions generate more than one microcode.

If the assembler is invoked with option "-w" it adds a warning for every 'next' where it succeeded to postpone the previous instruction, so you can verify whether this is ok. This warning is suppressed if you add 'post' into a comment on this line. In contrast, if it finds a 'post' and it could not postpone the previous instruction, this is counted as an error.

ret [cc]

Pop a return address from the machine stack. This performs a microcode jump to the loaded address.

Most times results in the two codes:

    ld cmd,(sp++)
nop : bit15

In addition, the assembler tries to postpone the last microcode before 'ret' into the trailing 'nop'. There are many obstacles but in many cases this is possible.

If the assembler is invoked with option "-w" it adds a warning for every 'ret' where it succeeded to postpone the previous instruction, so you can verify whether this is ok. This warning is suppressed if you add 'post' into a comment on this line. In contrast, if it finds a 'post' and it could not postpone the previous instruction, this is counted as an error.

The 'ret' instruction can be made conditional by appending a condition to it. The condition can be negated with '!':

    ret c       // return if flag c is true
ret !z // return if flag z is false
ret 1 // puts the ret code into plane 1, resumes in plane 0
ret 0 // vice versa

'ret 0' or 'ret 1' may be useful if you want to put 'one last instruction' between the tested code and return:

    and alu             // test alu
ld (a0),alu : z // store it somewhere; test cond. = flag z
ret 0 // return if flag z = false

jp [cc]

Jump to a microcode address. The address can be a data register, an address register or a label.

When loading the jump address from a register, a nop with condition code 'bit15' is appended. In addition, the assembler tries to postpone the last microcode before 'jp' into the trailing 'nop'.

If the assembler is invoked with option "-w" it adds a warning for every 'jp' where it succeeded to postpone the previous instruction, so you can verify whether this is ok. This warning is suppressed if you add 'post' into a comment on this line. In contrast, if it finds a 'post' and it could not postpone the previous instruction, this is counted as an error.

Currently jumps to a register cannot be conditional. You could use 'if'..'then' for this.

When jumping to a label, the destination address is stored in a 'data' code following the 'jp' code with condition code 'bit15', to extract the target plane from data bit 15. Jumps to a label cannot postpone the previos instruction, because they need the following code for the address. Jumps to labels can be made conditional.

    jp  label_1
  jp z,label_2
  jp 1,label_3

'jp 0,label' or 'jp 1,label' may be useful if you want to put 'one last instruction' between the tested code and the jump:

    and alu             // test alu
ld (a0),alu : z // store it somewhere; test cond. = flag z
jp 1,nowhere // jump nowhere if flag z = true

jsr

Call subroutine at the given microcode address. The address can be a data register, an address register or a label.

This is a 4 or 5 byte microcode. First the return address is pushed to the machine stack in ram, which takes 3 microcodes, and then a jp instruction is appended. In the pushed return address bit 15 is set to reflect the code plane from which the call originates, and bit 14 is set to disable the 'clear low byte' feature. So 'ret' can use this to return to the proper address and code plane.

Conditional calling and code postponing is currently not implemented.

    call foo
ld d4,(bar)
call d4

push <data> [ , ... ]

Pushes data on the machine stack. Pushing data registers generates these two codes:

    dec sp
ld (sp),<dreg>

Two codes are required, because accessing the ram with predecrement is not possible. The address adder is in the input path to the address registers, therefore only postincrement and -decrement are possible. Implementing predecrement would require a decrementer in the output data path of each supporting register and some more trickery (or a wait cycle :-O).

Multiple data items can be pushed in one go, requiring only one additional code for the first predecrement. The assembler uses postdecrement for the inner decrements.

Push can take almost every data source that also 'ld' supports. But not all are already implemented, some are still 'TODO'. I'll add them when actually needed.

    push 1234       push immediate value
push a0,d1 push address register and data register
push (a0) push ram at address a0
push (a0++) with postincrement and -decrement
push (++a0) with preincrement and -decrement

Pushing registers is probably the main domain of this instruction.

Some data sources need a temp register for scratch. If warnings are enabled then the assembler issues a warning for each instruction, which uses a temp register, either dtmp or atmp. This warning can be suppressed by putting 'atmp' or 'dtmp' into the comment on this line.

pop <dest> [ , ... ]

Pops data from the machine stack. Popping data registers generates this code:

    ld  <dreg>,(sp++)

Multiple data items can be popped in one go, but take care that you use the reverse order as when pushed.

Pop can store into almost every data destination that also 'ld' supports. But not all are already implemented, some are still 'TODO'. I'll add them when actually needed.

When the alu is the destination, then the assembler sets the appropriate option lines for the alu to 'load'.

    pop a0,d1       pop address register and data register
pop (a0) pop ram at address a0
pop (a0++) with postincrement and -decrement
pop (++a0) with preincrement and -decrement

Restoring registers is probably the main domain of this instruction.

Some addressing modes need a temp register for scratch. If warnings are enabled then the assembler issues a warning for each instruction, which uses a temp register, either dtmp or atmp. This warning can be suppressed by putting 'atmp' or 'dtmp' into the comment on this line.

ld <dest> , <source>

The most generic load opcode. Moves data from source to destination, most in 1 microcode. Not all possibilities are already implemented, some are left 'TODO' and i will add them as i require them.

Registers can be loaded from each other. When this requires address bus to data bus crossing, the appropriate 'bridge' units are incorporated into the code. Loading an address register from another address register without offset is not possible, due to the design of the address incrementer prepended to the address register inputs. Displacement 'dis' is the low byte from the last address loaded into the control unit, e.g. from an assembler opcode.

The busses, especially the address bus, cannot be used twice in the same cycle, e.g. loading an address register from ram requires an intermediate step in the dtmp scratch register. If warnings are enabled then the assembler issues a warning for each instruction, which uses a temp register, either dtmp or atmp. This warning can be suppressed by putting 'atmp' or 'dtmp' into the comment on this line.

Whenever the alu is the destination of a move, then the assembler sets the appropriate option lines for the alu to 'load'.

Some examples of addressing mode combinations:

    ld  d0,d1   
ld d0,a0
ld d0,(a0++)
ld d0,123
ld d0,(123)
ld d0,--a0
ld d0,(a0+=dis) // add displacement after addressing ram
ld a0,d0
ld (a0++),d0
ld a0,a1+dis
ld (123),a0
ld (a0++),(a1++)

ex <dreg>, (<areg>)

Swap data register with top contents of machine stack.

In essence does the following:

    ld  dtmp,(areg)
ld (areg),dreg
ld dreg,dtmp

Obviously it uses the scratch data register. I just added this for my convenience while converting some z80 assembler source.

and | or | xor   <data>

These instructions implicitely use the alu as target and for the 'other' argument for the binary functions. Loading data into the alu always requires setting of up to 4 option lines, which are choosen to be disjunct with the 2 option lines of the address adder, so any addressing mode is possible.

An immediate argument nn is stored in a subsequent 'data' microcode. All other instructions are only 1 microcode.

At this point some flags are influenced which can be tested in the next microcode, which is the 'data' microcode in the case of an immediate argument:

    and d0              // sets z, cy, and ovfl
  if z // generates a nop with z-flag test ('nop : z')
  add 1 // optional code for z=1
  then // join branches. Will put a nop in the '0' plane
  // and set cond to same plane in both codes
 
  xor $8000 :d15 // d0 and d15 are also set. here the d15 test is added to the data microcode
  if 1
  jp ari_neg // optional code for d15=1
  else
. jp ari_pos // pairing optional code for z=0
  then // join branches. may produce some unneccessary, unreachable code.

cpl <data>

This instruction loads the data source complemented into the alu register. This is the same as the 'ld' opcode does except for the complementation.

tst <data>

TODO

equ <data>

TODO

add | add_c | sub | sub_nc   <data>

These instructions implicitely use the alu as target and for the 'other' argument for the binary functions. Loading data into the alu always requires setting of up to 4 option lines, which is handled by the assembler.

These instructions use the adders which are slower due to the carry propagation. Therefore these instructions generate a preceeding dummy operation to give more time for the result to establish, and because of that, these instructions cannot take an immediate argument itself. Therefore, immediate arguments are loaded into the dtmp register first. If warnings are enabled then the assembler issues a warning for each instruction which uses the temp register. This warning can be suppressed by putting 'dtmp' into the comment on this line.

the next instruction after add can test the flags to divert the flow of code and the next after that can contain different code in both planes.

'add' adds alu and data with cy=0. You cannot add the carry flag, because flags are not stored. Instead you must divert into the planes and use 'add' (without cy) or 'add_cy' (with carry).

The same is true for 'sub'. Because 'sub' actually adds the complement, which is -n-1, the '-1' must be undone by adding with the cy flag set if you intend to subtract the other data without offset. This is what 'sub' does. If you want to subtract a carry, then you must use 'sub_nc' which subtracts with cy=0.

cmp | cmp_nc   <data>

This is the same as 'sub' and 'sub_nc', except that the result is not loaded into the alu.

Flow Control

do loop until while if else then

TODO

Example Header Part of a Microcode Source File:

// ==============================================================================
// Symbolic Names:
 
// Data Bus:
  dreg cmd = 0 // cmd input & ival output
  dreg ival = 0 // ""
  dreg mem = 1 // internal ram
  dreg io = 2 // k1-bus i/o
  dreg axd = 3 // a-to-d port
  dreg d0 = 4 // gp dreg register
  dreg d1 = 5 // ""
  dreg d2 = 6 // ""
  dreg d3 = 7 // ""
  dreg alu = 8 // top of stack = tos = akku = alu
  dreg dxxx1 = 9 // unused
  dreg swap = 10 // swap hibyte <-> lobyte
  dreg dxxx2 = 11 // unused
  dreg sr = 12 // shift right
  dreg sl = 13 // shift left
  dreg msbit = 14 // 16-to-4 encoder
  dreg cmd_lo = 15 // cmd.lo & byte conversion (r/-)
  dreg d2ar_clk= 15 // d-to-a register (w/-)
  dreg dnop = axd // dummy target
  dreg dtmp = d3 // scratch
 
// Address Bus:
  areg ip = 0 // instruction pointer
  areg dxa = 1 // d-to-a port
  areg sp = 2 // stack pointer
  areg a0 = 3 // gp address register
  areg a1 = 4 // ""
  areg a2 = 5 // ""
  areg a3 = 6 // ""
  areg d2ar = 7 // d-to-a register (r/-)
  areg ioaddr = 7 // k1-bus i/o address (w/-)
  areg anop = dxa // dummy target
  areg atmp = a3 // scratch
 
// Generic Option Line Names:
  opt opt0 = 1<<14
  opt opt1 = 1<<15
  opt opt2 = 1<<16
  opt opt3 = 1<<17
  opt opt4 = 1<<18
  opt opt5 = 1<<19
 
// ALU:
  opt alu_cpl = opt3 // complement input
  opt alu_cy = opt2 // carry input
  opt cy = alu_cy:1
  opt nc = alu_cy:0
 
  opt alu_fu = 3<<14
  opt add = alu_fu:0 + alu_cpl:0 // + alu_cy
  opt sub = alu_fu:0 + alu_cpl:1 // + alu_cy
  opt xor = alu_fu:1 + alu_cpl:0
  opt equ = alu_fu:1 + alu_cpl:1
  opt and = alu_fu:2 + alu_cpl:0 + alu_cy:0
  opt or = alu_fu:2 + alu_cpl:0 + alu_cy:1
  opt load = alu_fu:3 + alu_cpl:0
  opt load_cpl= alu_fu:3 + alu_cpl:1
 
// Address Adder:
  opt a_cpl = opt4
  opt a_dis = opt5
  opt a_bits = 3<<18 // a_cpl+a_dis
  opt add0 = a_cpl:0 + a_dis:0 + dxa.oe
  opt add1 = a_cpl:0 + a_dis:0 // && !dxa.oe
  opt sub1 = a_cpl:1 + a_dis:0 // && !dxa.oe
  opt adddis = a_cpl:0 + a_dis:1 // + CMD_LO
  opt subdis = a_cpl:1 + a_dis:1 // + CMD_LO - 256
 
// Misc. Options:
  opt cmden = 1<<20:0
  opt cmddis = 1<<20:1
 
// Conditions:
  cond 0 = 0 // (stay in) code plane 0
  cond 1 = 1 // (stay in) code plane 1
  cond cy = 2 // fork dep. on ALU carry flag
  cond z = 3 // fork dep. on ALU zero flag
  cond ovfl = 4 // fork dep. on ALU signed overflow flag
  cond rnd = 5 // fork dep. on RND
  cond bit0 = 6 // fork dep. on data bus bit 0
  cond bit15 = 7 // fork dep. on data bus bit 15
 
// DIV_FF Names:
  div clock2 = 0 // 0 => XTAL/2
  div clock4 = 1 // 0 => XTAL/4
  div halt = 2
  div ei = 3
  div led_grn = 4
  div led_yel = 5
  div led_red = 6
  div reset = 7
 
 
/* ======================================================
RESET
 
Reset conditions:
• $3FFF:1 with 'cmddis=1' and 'cond=0' (for next instr) forced by INIT
• $0000:0 is executed
*/
: reset = $0000:0
di
clr halt
#if 1 // full speed:
set clock4 // 1:1 (not 1:4)
set clock2 // 1:1 (not 1:2)
#else // half speed:
set clock4 // 1:1 (not 1:4)
clr clock2 // 1:2
#endif // leds:
set led_red // red: ON
clr led_yel
clr led_grn
// ... more here ...
clr led_red
jp $ // wait for power-off...
 
 
// =======================================================
// interrupt vectors
// interrupts jump to $0000:1 ... $3000:1 and $1000:0 ... $3000:0
 
: IRPT1 = $1000:0
  jp irpt3
 
: IRPT2 = $2000:0
  jp irpt3
 
: IRPT3 = $3000:0
  dec ip : 1 // ... and join handler in other plane
  //jp irpt3
 
: IRPT4 = $0000:1
  jp irpt3
 
: IRPT5 = $1000:1
  jp irpt3
 
: IRPT6 = $2000:1
  jp irpt3
 
: IRPT7 = $3000:1
irpt3: dec ip // restore supressed opcode
  next // MORE TODO

Material

Name Letzte Änderung Länge 
asm/ 2019-08-20 05:21 20 
GC/ 2019-08-20 05:21
log/ 2019-08-20 05:21
main_out/ 2019-08-20 05:21 11 
SIO_firmware/ 2019-08-20 05:21
test/ 2019-08-20 05:21 10 
99_bottles_of_beer.asm 2015-12-22 16:10 2323 
99_bottles_of_beer.cc 2015-12-20 13:39 14927 
_info.txt 2013-04-08 15:31 602 
bcc.asm 2015-12-22 16:10 22164 
bcc.cc 2015-12-03 15:46 12329 
bcc.h 2015-12-03 15:46 4524 
bcc_opcodes.h 2015-12-03 15:46 8586 
Block.asm 2015-12-22 15:49 5661 
Block.cc 2015-12-03 15:47 5205 
Block.h 2015-12-03 15:46 2458 
BlockDevice.asm 2015-12-22 16:10 6125 
BlockDevice.cc 2015-12-03 15:46 6298 
BlockDevice.h 2015-12-07 16:17 3270 
dev.asm 2015-12-22 16:10 18174 
dev.cc 2015-12-07 16:16 12974 
dev.h 2015-12-03 15:46 3138 
Device.asm 2015-12-22 16:10 865 
Device.cc 2015-12-03 15:46 2442 
Device.h 2015-12-03 15:46 3093 
dict_neu_denk.h 2010-09-23 18:12 4430 
Dir Kopie.cc 2011-09-30 20:08 43389 
Dir.asm 2015-12-22 15:49 35315 
Dir.cc 2015-12-03 15:46 49566 
dualsio.cc 2012-04-26 17:16 20240 
errno.h 2015-12-03 15:44 2282 
FD.asm 2015-12-22 16:10 12680 
FD.cc 2015-12-22 15:29 16827 
FD.h 2015-12-07 16:16 8547 
File.asm 2015-12-22 15:49 20177 
File.cc 2015-12-03 15:46 18896 
File.h 2015-12-03 15:46 7217 
float taylorreihe test.vs 2017-07-04 21:25 3392 
float.asm 2015-12-22 15:49 49128 
float.cc 2015-12-03 15:46 29397 
float.h 2015-12-03 15:46 4277 
float_loge_tests.txt 2010-09-13 16:08 1959 
globals.cc.h 2015-12-03 16:37 3549 
i2c-eeprom with eefs filesystem.txt 2014-10-21 17:00 13234 
i2c-eeprom.pdf 2014-04-11 22:09 282250 
i2c-eeprom.rtf 2014-04-11 22:02 115222 
i2c.asm 2015-12-22 16:10 9336 
i2c.cc 2015-12-07 16:16 11737 
i2c.h 2015-12-07 16:16 4323 
long.asm 2015-12-22 16:10 17840 
long.cc 2015-12-03 15:46 16845 
long.h 2015-12-03 15:46 3895 
main.asm 2015-12-22 18:30 9478 
mc.pro 2015-12-21 17:30 2034 
options.h 2015-12-22 19:38 4089 
SerialDevice.asm 2015-12-22 16:10 6775 
SerialDevice.cc 2016-06-08 20:26 7015 
SerialDevice.h 2015-12-03 15:46 3564 
shell.asm 2015-12-22 16:10 10839 
shell.cc 2015-12-21 18:02 8624 
shell.h 2015-12-03 15:46 1638 
sio.cc 2015-12-03 15:46 26254 
sio_eeprom.bin 2013-04-13 18:30 1714 
sio_eeprom.h 2012-06-17 17:53 10204 
sort.asm 2015-12-22 16:10 2893 
sort.cc 2012-04-25 18:16 5346 
sort.h 2015-12-03 15:46 1761 
stdc.asm 2015-12-22 16:10 5571 
stdc.cc 2015-12-03 15:46 4543 
stdc.h 2016-12-08 16:46 18786 
str.asm 2015-12-22 16:10 17246 
str.cc 2015-12-03 15:46 11598 
str.h 2015-12-03 15:46 3302 
types.h 2015-12-03 17:37 1692 
untitled.txt 2012-04-24 15:30 3344 

powered by vipsi - your friendly VIP Script Interpreter

Valid HTML Valid CSS