Intel 8049 (8048, 8748, 8749, 8035, 8039) / MCS-48 Hardware-Debugger & Monitor
When I got my hands on the 8049 microcontrollers once again in 2021, I was a bit curious as to whether there were any signs of life left in this truly archaic component and what it was like to program it. After all, the MCS-48 family was very popular from the late 1970s to the end of the 1980s.
However, a RAM should be used as program memory in order to avoid the cumbersome handling of (E)EPROMs, programming and erasing devices (where are they actually?) this time: an integrated EPROM simulator.
A power supply was connected and the only on-chip hardware was the oscillator with a crystal and capacitor with *RESET=L and EA=H.
It was actually expected that only at the oscillator connections XTAL1 and XTAL2 of the µC activity would be measured, but when EA=H the MCS-48 µCs generate even in the reset-state signals at *PSEN and ALE and continuously output the address 0x000 on BUS and P2.0 to P2.3 (This is what it says in the "Intel 8-bit embedded controllers" manual from 1990: "[...]ALE and PSEN (if EA = 1) are active while in reset." Source: "Intel 8-bit embedded controllers" manual (1990); chapter "Single Component MCS48-System" / "2.12 Reset"; p.1-10, which I had obviously overlooked). Unfortunately, this thwarted the idea of being able to simply load the RAM used as program memory with data when isolated from the 8049 in the reset-state.
Image: *PSEN and ALE with *RESET=L und EA=H
The simplest way to do a further test was a "NOP loop": the CPU only finds "NOP" instructions (No OPeration) in its address space. If these NOP-instructions are executed by the CPU, the incrementing of the program counter (PC), which always points to the program memory address of the next instruction to be executed, can be observed on the external address signals.
Among other things, oscillator, program counter (PC), output and demultiplexing of the address information, reading of the opcode, instruction decoder and the internal state machine have to work correctly for this.
In order not to have to program an EPROM or other device for this and since it is only the opcode for "NOP" (0x00), a 74HC541 (Octal 3-State Buffer) was used as a hard-wired instruction memory, which places the opcode on the BUS lines while *PSEN=L.
Instead of the "transparent latch" 74LS373 proposed by Intel for storing the lower 8 address bits of BUS an edge-triggered D-flip-flop with tri-state outputs was provided as well as for the lower nibble (P2.0 to P2.3) of P2, because this allows to achieve a less noisy address bus.
Since the address lines of the RAM have to be isolated from the 8049 during data loading via the interface between the personal computer and the debugger/monitor hardware, the tri-state outputs of these D-FFs were of course very welcome.
And because the bidirectional data bus of the RAM also has to be separated from the 8049 when loading data, a "Bi-Directional 8-Bit Bus Transceiver", 74HC245, was also used for the NOP loop test. *PSEN, *RD, *WR, ALE were provisionally buffered so that even in the event of accidents on these lines, the 8049 was not at risk of damage.
Image: Executing the NOP loop (D0...D7, P2.0...P2.3, ALE, *PSEN, *RESET)
Image: The circuit used for the NOP test
In order to be able to use the same RAM as an external program memory and as a data memory that can be written to or read using the "MOVX @Ri,A" and "MOVX A,@Ri" instructions and the *WR and *RD signals of the µC, the *PSEN and *RD signals of the µC were linked together by an AND-gate, so the *OE line of the RAM always assumes a low level as soon as one of these signals becomes active (low).
The *PSEN signal of the µC became the 17th address line, A16, of the RAM, so that the external program memory area in the RAM extends from address 0x00000 to address 0x0FFFF and the external data memory area extends from address 0x10000 to address 0x1FFFF (without debugger and without memory-mapped IO).
The 8049 can address 4kByte with the at maximum 12 address lines under its control. The additional address bits are provided by three 4-bit D flip-flops in the MMU (Memory Management Unit) of the debugger/monitor system. This makes it possible to address up to 64kByte for each of the program- and data-memory, independently selectable under software control (see "3.6.0 DeMon48_128k Memory Organization / MMU").
For the control of the DeMon48_128k system and as the interface to the personal computer an old, but here still as remaining stock available PIC16F874 was chosen. A first version of the debug/monitor system was based on serial communication between the Debug-µC and the rest of the hardware, but this was later changed to parallel data exchange in order to make the system much faster.
The first test firmware for the Debug-µC had the following function:
After starting, the same signals as in the test with the 74HC541 as "program memory" could be measured on the address and data lines of the 8049, so the correct opcode for "NOP" was really read from different addresses in the RAM.
This test should prove the separation between program and data areas in the RAM. To do this, the following MCS-48 program writes to a 256-byte page in the data area of the external RAM the complement of the respective address, i.e. 0xFF in address 0x00, 0xFE in address 0x01, 0xFD in address 0x02 to 0x00 in address 0xFF:
If the separation of data and program memory areas does not work, the program overwrites itself, which would be detectable by the absence of low pulses on the *WR line.
Image: 256Byte Page Write (D0...D7, P2.0...P2.3, ALE, *PSEN, *RD, *WR)
Image: 256Byte Page Write (D0...D7, P2.0...P2.3, ALE, *PSEN, *RD, *WR) (Continued)
The single-step circuit implemented with the 74HC74 and 74HC00 devices is active when the *SET input of the D-FF connected to the MCS-48 µC's *SS input is at H-level (see set of drawings). If a L-level is present here, the output of the D-FF is constantly set to H-level. The H-level at the *SET input enables the L-level of the inverted ALE signal to reset the output of the D-FF to L-level, which activates the MCS-48 µC's *SS input.
The MCS-48 µC then stops executing the current instruction (in case of instructions that require 2 instruction cycles to execute, the second instruction cycle is completed) and signals the active single-step mode by setting ALE to H. During this state, for which there is no time limit on the part of the MCS-48 µC, the address of the next instruction to be executed is output on BUS and the lower half of P2.
In order to execute the next command, a rising edge at the clock input of the D-FF causes the H-level that is constantly present at the D input of the D-FF to appear at the *SS input of the MCS-48 µC. This releases the single-step logic in the MCS-48 µC, the next command is addressed and the L-level at the ALE output of the MCS-48 µC, which is used to temporarily store the lower 8 bits of the address signals, resets the D-FF, *SS is set to L-level and the system is finally back in the waiting state in which the address of the next command is output at BUS and the lower half of P2.
To exit the single step mode can be forced at any time by applying a L-level to the *SET input of the D-FF.
Image: Single-Step Hardware
Image: Single-step activation by *S=H at the D-FF (last instruction consists of 2 ALE cycles)
Image: Execution of individual instructions (1&2 cycles) by pulses at the CLK input of the D-FF
In order to fully utilize the available 128kB memory space in the static RAM of the DeMon48_128k system, the 12 bit address information provided by the MCS-48 µC's address counter and MB-FF is obviously not sufficient (2^12 = 4096dec.).
Therefore, three 74HC173 4-bit D flip-flops independently provide 4 bits for program-memory accesses and 8 bits for data-memory accesses as address signals, which use the "PROG" signal otherwise only required for the operation of the rather obsolete 8243 port expanders (16 additional I/O lines in a large (600mil) 24-pin DIL package) to create the desired state of the additional address signals under software control.
The 17th address line, A16, is provided by the MMU (Memory Management Unit) depending on the current memory configuration (see: "5.0.0 Memory configurations").
Since the provision of the additional address bits takes place under full control of a user program running on the MCS-48 µC, programs that use up to 16 4kByte banks (=64kB) of the program- and up to 256 256Byte pages (=64kB) of the data-memory area (the debugger occupies 512 bytes in the RAM) can actually be created.
The active 256-byte data-memory page is selected via the "DBR" (Data Bank Register), which consists of DBL for the 4 address bits A8 to A11 and DBH for the 4 address bits A12 to A15.
Image: Access to DBH via PROG and effect on A12...A15
If a 4(8)kByte memory map is configured (see "5.0.0 Memory configurations"), for example to be able to use the "PROG" signal with 8243 port expanders, DBL (A8 ... A11) can be automatically loaded with the current state of P2.0 ... P2.3 in each ALE cycle by means of the appropriate jumper configuration, so that the active 256Byte data-memory page or the I/O area is selected via P2.0 ... P2.3 ("OUTL P2,A") and the then transparent DBL register.
The active 4kByte program-memory bank is selected via the "PBR" (Program Bank Register). When changing the 4kB program-memory bank, 16 address bits can be manipulated simultaneously due to the MMU hardware, so that, for example, an unconditional jump from 0x08E7 to 0x53F0 is executed:
The "SEL MBx" command at the beginning of the sequence may be optional in other cases, but the sequence "MOVD/ORLD" followed by a command to manipulate the program counter ("JMP" / "CALL" / "RET") is strictly required!
Image: Change from 4kB program memory bank 7 to 4kB bank 8
The MMU supports subroutine calls across 4kB bank boundaries, but the 4kB bank to which "RET" returns has to be stored, for example, in a register:
If a hardware that uses one or more 8243 port expanders is to be tested with the debugger, the storage of the additional address bits A12 to A15 using the "PROG" signal can be switched off via a jumper (see "5.0.0 Memory configurations"). The system then behaves like an 8049 with 4kB of external program-memory and 3.75kB to 4kB (depending on whether an I/O area is enabled) of external data-memory, and the "PROG" signal is free to control the 8243 port expanders.
The MMU can be used to force all memory accesses of the MCS-48 µC to occur with A16=Low, regardless of whether they are program- or data-memory accesses. To do this, the signal DA16 (Data Address 16) is set to "Low" using "ORLD Pp,A" instructions. This achieves something normally impossible: the MCS-48 µC can write to its own program-memory area under software control. This enables, among other things, programs to be loaded by software that runs on the MCS-48 µC itself.
Of course, DA16 can also be set to "High" again in software, so that, for example, a program is first loaded with DA16=Low and then, during its execution with DA16=High, 64kB as program-memory and additional up to 64kB as data-memory are available again.
In addition, this switching option has transformed the Harvard-architecture of the MCS-48 µC into a true Harvard-/Von-Neumann-architecture-hybrid. It has a Von-Neumann-architecture with a shared memory for program and data when DA16=Low and a Harvard architecture with two logically separate memories for program and data when DA16=High.
In addition to the PBR and DBR, a 4-bit "Expansion Register" / "EXP" is connected to the BCR (Bank Control Register) interface of the MMU.
The "DIS_IO" bit is used to enable (DIS_IO=1) and disable (DIS_IO=0) a memory-mapped I/O area in the address space under software control (see "3.6.8 Memory-Mapped I/O").
Bit | Function |
---|---|
3 | DIS_IO |
2 | None |
1 | None |
0 | None |
DA16 = H, A16 = normal :
MOVD P4,A | Load Data Bank Register Low Nibble (DBL : A8 ... A11) |
MOVD P5,A | Load Data Bank Register High Nibble (DBH : A12 ... A15) |
MOVD P6,A | Load Program Bank Register (PBR : A12 ... A15) |
MOVD P7,A | Load Expansion Register |
DA16 = L, A16 = forced low :
ORLD P4,A | Load Data Bank Register Low Nibble (DBL : A8 ... A11) |
ORLD P5,A | Load Data Bank Register High Nibble (DBH : A12 ... A15) |
ORLD P6,A | Load Program Bank Register (PBR : A12 ... A15) |
ORLD P7,A | Load Expansion Register |
Access to program memory:
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MM16 | PBR.3 | PBR.2 | PBR.1 | PBR.0 | PC.11 | PC.10 | PC.9 | PC.8 | PC.7 | PC.6 | PC.5 | PC.4 | PC.3 | PC.2 | PC.1 | PC.0 |
Access to data memory and I/O:
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MM16 | DBH.3 | DBH.2 | DBH.1 | DBH.0 | DBL.3 | DBL.2 | DBL.1 | DBL.0 | Ri.7 | Ri.6 | Ri.5 | Ri.4 | Ri.3 | Ri.2 | Ri.1 | Ri.0 |
Access to the debugger's program and data memory:
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
"1" | "1" | "1" | "1" | "1" | "1" | "1" | "1" | MM8 | PC.7 | PC.6 | PC.5 | PC.4 | PC.3 | PC.2 | PC.1 | PC.0 |
Execution of the instruction in address 0x1FE09 in the debug program memory:
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
"1" | "1" | "1" | "1" | "1" | "1" | "1" | "1" | MM8 | "0" | "0" | "0" | "0" | "1" | "0" | "0" | "1" |
Signal | Function |
---|---|
MM8 | MMU A8 output |
MM16 | MMU A16 output |
PBR | Program Bank Register |
DBL | Data Bank Register Low Nibble (A8 ... A11) |
DBH | Data Bank Register High Nibble (A12 ... A15) |
PC | Program Counter |
Ri | Contents of the index register (R0, R1) used in "MOVX" instructions |
"1" | Is held at logic "1" (+5V = H) by the Debug-µC |
"0" | Is kept at logical "0" (0V = L) by the Debug-µC |
Address space of the basic configuration (see "5.0.0 Memory configurations"):
Start (h) | Ende (h) | Bezeichnung |
---|---|---|
00000 | 0FFFF | MCS-48 program-memory |
10000 | 1FCFF | MCS-48 data-memory (external) |
1FD00 | 1FDFF | Memory Mapped I/O |
1FE00 | 1FEFF | Debug program-memory |
1FF00 | 1FFFF | ebug data-memory |
With the aid of an "8-bit equality comparator" 74HC688, the IO/*M signal is created from the address signals A8 to A16, which allows 256 I/O addresses to be mapped into the address space for the external data memory. External I/O units can thus be addressed like an external RAM using the "MOVX" instructions of the MCS-48 instruction set, so that 256 * 8 bits (=2048 bits) can be addressed for output and 256 * 8 bits (=2048 bits) for input. The IO/*M signal has H-level in the I/O address area and L-level when the program- or data-memory is accessed.
If the "DIS_IO" bit provided by the MMU's expansion register is set or Jmp2 is not installed, the I/O area is disabled and the whole program- and data-memory is accessible.
Image: IO/*M signal for memory-mapped I/O with DeMon48
For information on the jumpers shown, see "5.0.0 Memory Configurations".
The DeMon48 system currently provides one hardware breakpoint (a breakpoint RAM may be added later), which allows code to be executed without any loss of speed while the breakpoint is active.
Due to the multiplexing of DBR and PBR with the *PSEN signal, it is necessary to delay and shorten the *X_PSEN signal using two RC networks so that the point in time at which the address bus is read is shifted approximately to the middle of the original *PSEN signal.
Image: Hardware-Breakpoint circuit
Image: Delayed and shortened gate signal for the "74HC688" 8-bit comparators
The 11MHz clock for the MCS-48 µC is generated via a separately constructed crystal oscillator connected to a switching circuit. The circuit enables switching between two signal sources (internal oscillator and external clock, square wave, 50% duty cycle, 5Vpp) without incomplete oscillator cycles ("glitches") occurring at the clock input of the MCS-48 µC.
The debug-µC automatically connects the 11MHz oscillator to the clock input of the MCS-48 µC while the monitor routines are being executed and to the external clock input while the user program is being executed, if this is selected by the user by setting the "Select" signal to "1".
Alternatively, the "Select" signal can be used to constantly connect the 11MHz oscillator to the clock input of the MCS-48 µC by setting it to "0".
The timing of the Debug-µC when executing the MCS-48 user program in single-step mode is adjusted to the frequency entered in the GUI of the then selected clock source.
If the option of using an external clock is not required, the separate crystal oscillator and the switching circuit can be omitted and an 11 MHz crystal with capacitor be connected directly to the oscillator terminals of the MCS-48 µC (see "2.3.0 Minimal system with external program memory").
Image: Clock generation and glitchless switching