3

I am currently trying to create a FPGA styled simulator of the 8080 in C. I have a couple of questions regarding the D0-D7 pins. As far as I can see the D0-D7 lines are used in order to store data (a byte).

But the Intel 8080 System User's Manual mentions how each and every line are set depending on an event (see 2.6, page 20). I tried replicating how the 8080 fetches, reads and writes data through code. So if a read is happening these pins will be set:

D7 - Designates that the data bus will be used for memory read data.

SYNC

DBIN

But also the confusing part is that on the diagram below that explains the functions of D0-D7 mentions how D1, D5, D7 pins are set when fetching which makes the total number of pins set to (1) 5.

I really would appreciate if anyone took their time to further explain how these pins work when doing operations such as read, write, fetch and what I should keep in mind and what is important during the operations and what lines need to be set to 1.

As I've mentioned I have tried implementing this through code and this is what I've done:

static inline uint64_t
_i8080_set_addr_pins(i8080_t* const cpu, uint16_t addr, uint64_t pins) { // Mainly used for reading, fetching and incrementing the PC.
  return (cpu->pins & ~0xFFFF) | addr | pins;
}

static inline uint64_t 
_i8080_set_addr_byte_pins(i8080_t* const cpu, uint16_t addr, uint8_t byte, uint64_t pins) // Mainly used for writing addresses or registers (single or pair) to D0-D7 and A0-A15. 
{
  return (cpu->pins & ~0xFFFFFF) | addr | (byte << 16) | pins; 
}

// Reading and writing an address or byte to the pins


static inline uint64_t
_i8080_cpu_read_pins(i8080_t* const cpu, uint16_t addr) {
  return _i8080_set_addr_pins(cpu, addr, I8080_D7 | I8080_SYNC | I8080_DBIN);
}

static inline uint64_t
_i8080_cpu_write_pins(i8080_t* const cpu, uint16_t addr, uint8_t byte) {
  return _i8080_set_addr_byte_pins(cpu, addr, byte, I8080_D1 | I8080_SYNC | I8080_WR);
}

// Fetches and moves on to the next instruction. Sets all the necessary pins to .

static inline uint64_t 
_i8080_fetch(i8080_t* const cpu) {
  // Increments PC, sets D5, SYNC and DBIN completing a fetch, and SYNC sending a signal to different hardware components.
  return _i8080_set_addr_pins(cpu, cpu->pc++, I8080_D7 | I8080_D5 | I8080_SYNC | I8080_DBIN);
}

I am using this PDF for reference.

2
  • 1
    What would appr. happen is that in the first cycle the contents of the databus would be latched into a register, forming the control bus. In the cycle after that, the CPU would put data onto the databus, which can then be written to the peripheral or memory selected by A0..15. When reading, the contents of the databus would be read by D0..D7. The databus is multiplexed with the control bus. Other people here can probably give more detailed answers.
    – chthon
    Commented Jan 31 at 12:37
  • 1
    Have you read the whole section describing the processor cycle (p.2.3 - 2.10), especially the part about Machine- and Clock-cycles and how they are structured?
    – Raffzahn
    Commented Jan 31 at 13:18

1 Answer 1

9

Basics

The data bus carries time multiplexed information:

  • At the end of the first clock cycle (*1) of each machine cycle (*2) it always carries the status signals as described on Page 2-6.
  • During the following clock cycles it may be used to read or write data.

What information is carried at what time depends on clock and machine cycle (*1) executed. Pages 2.3 to 2.10 of the 1977 Intel MCS80 User's Manual describe all cycles in great detail, followed by several drawings.

Why?

Time multiplexing of those signals was chosen to reduce pincount. Those signals can be made available by adding an 8 bit latch controlled by SYNC (*3).

Which Means?

Unlike with the 8085, where operation is made in a way to allow derivation of all signals necessary to complete a bus cycle to It needs to somehow add those signals to the simulation structure/API.

Solution A: Staying Very Close

The code suggests your that your emulation is intended to work on chip level. If so, then it must as well present the whole structure of M and T cycles(*4). And everyone using it must of course 'connect' a virtual latch chip (*5) to derive the status signals at the end of T1, so it can distinguish for example between memory or I/O access.

This may, depending on your project structure, require need quite some reworking like adding a layer for the latch and it's usage. Also, depending on project goal, it may add execution load otherwise unnecessary. An alternative would be not going as deep and staying at machine cycle level.

Solution B: Staying Close Enough

The example shown makes me believe that architecture chosen is intended to work not full T-cycles, but more abstract M-cycle level, or even above that. If that's true, it would be fine to remove the whole time multiplex part and 'integrate' the virtual 8212 latch into your virtual 8080.

API wise this means extending the I8080 vector in pins by adding 8 more signals (*6) which might be name (*7) as

  • I8080_INTA
  • I8080_WO
  • I8080_STACK
  • I8080_HLTA
  • I8080_OUT
  • I8080_M1
  • I8080_INP
  • I8080_MEMR

Think of the result as a special version, or a little sub-board carrying those two chips plugged into a system not knowing of the multiplexing.

Fetch for example could now look (*8) like this:

static inline uint64_t 
_i8080_fetch(i8080_t* const cpu)
{ // Increments PC, sets STATUS, SYNC and DBIN completing a fetch and SYNC
  // sending a signal to different hardware components.

  return _i8080_set_addr_pins(cpu,
                              cpu->pc++,
                              I8080_D7 | I8080_SYNC | I8080_DBIN |
                              I8212_WO | I8212_M1 | I8212_INP | I8212_MEMR);

}

That way the simulation stays still on machine cycle level, but provides all signals necessary or other components to interact with.


On a side note: Skip the 8080 and go for the 8085 if you really invest time in this. It cleans out some of the 8080 issues, much like the Z80 does, while staying close to the basic Intel-design.


*1 - The 8080 processor cycle is structured into machine cycles, which there are 10 different types, listed on p2.5. Each machine cycle can consists of 3 to 5 clock cycles, marked as T1..5, depending on the cycle type.

During (normal, unmodified) instruction execution types 1..7 may occure. The detailed sequence for each machine cycle and clock cycle for every instruction is shown in a table on p2-16 to 2.19 (Too bad that Archive.Org's viewer doesn't keep the original page layout)

M-Cycle 8..10 are special situations for interrupt handling and bus sharing.

*2 - Or more correct on each machine cycle that is also an external cycle. Machine cycles that only carry out internal work, like M2/M3 of 16 bit adds or M2 of register/register operations, will not raise SYNC and thus not carry a status signal during T1.

*3 - 40 pin was an unusual huge package and expensive in the early 1970s, adding even more would have been prohibitive expensive. An 8 bit latch on the other hand was a cheap standard chip cutting cost on both sides.

*4 - That is if you're not going full in and present the real timing relations.

*5 - Much like the wiring of he 8212 shown on the top right drawing on p2-6

*6 - This should be fine as its 64 bit already, isn't it?

*7 - Personally I'd also include them into the vector, but prefix the signals rather as I8212_xxxx to show the deviation taken (second chip incorporated).

*8 - Yes, I very much prefer to break down complex function calls into parameters. Removes the need to guess where to look,thus speeding up reading and debugging a lot. Eventually the greatest advantage free source format languages brought us: No More Spaghetti-Source (*9).

*9 - Many people know Spaghetti Code, when the control flow is mixed up, wiggling around the screen, like noodles on your plate. Lesser known Spaghetti Source is when source lines hang off the sides of a screen, making handling and reading harder than it needs to be.

2
  • What if I try to actually make it very close almost identical, so have it on the T-cycle level. How different would it be from M-cycles? As far as I can see many lines changing state in a T-cycle multiple times. I will also try avoiding spaghetti code from now on. Thank you for warning me!
    – cdunku
    Commented Jan 31 at 16:50
  • @cdunku Clock (T) cycle accurate emulation doesn't make much sense, as it doesn't add any useful detail. Machine cycle is fine for next to all purpose. The next useful level below would be exact timing, according to chip timing which means working with variable emulation steps like when doing an FPGA timing. Not really useful unless you develop a chip - in that case better use standard tools. The only thing maybe useful would be adding up clock cycles within each machine cycle. That way the emulation provides exact timing o synchronise other items (and speed).
    – Raffzahn
    Commented Feb 3 at 9:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .