Start page: ARABART home
← All fifteen programs

Digi-Test

1993 · C:\DIGI_T\MAIN.EXE

C:\DIGI_T\MAIN.EXE
Digi-Test running in DOS

Loading MAIN.EXE, 129 KB

Click the screen to type into the program.

His graduation project: put real logic chips on a board, wire them up, and watch the circuit run.

The story

Digi-Test was the heart of the KEMYAM group's graduation project, and Yasser wrote the software. It is real object-oriented C++ from 1993. You place real chips on a sheet, wire them with the keypad, and it works out what the circuit does. To test a real board it gives every wire five values: 0, 1, unknown, and two marks that carry a fault to the output. Every chip and gate it knows is a small data file, so a new chip needs no new code. With the team's plug-in card, it drove the real circuit and told the technician exactly where to place the probe next.

KEMYAM, the circuit he defended

This is the circuit Yasser’s team showed at their graduation defense in 1993, drawn from the very file Digi-Test saved it in. Flip the inputs. His program’s own rules decide what lights up.

7411 7404 7408 7432 P1 P2 P3 P4 VCC P6 P7 P8 P9 P10 GND

P8 = 1

Inputs

Output P8 is 1

Digi-Test was the graduation project of five students in Port Said: Ebrahim El Etr, Hatem Moustafa, Khaled Zakaria, Mohamed Asaad and Yasser. You place real 74xx logic chips on a sheet, wire them with the keyboard, and the program works out what the circuit does. With the team’s own plug-in card, it also tested the real circuit on the bench and led the technician straight to the broken chip.

Yasser wrote it in C++, with a few lines of assembly where it touches the hardware: 131,248 bytes of his own code in about 540 functions. Picture an early Logisim with a hardware test rig attached. The board above is KEMYAM, from his file FIL0.CIR. It has 4 chips, 11 gates, 8 inputs, one output, 8 nodes and 38 wires, each one exactly where he drew it.

Every row, like an odometer

The PRINT menu sends the circuit’s truth table to the printer: every combination of inputs, with the output for each. To visit them all, Digi-Test counts like a car odometer. The first input flips on every row, and when it rolls back to 0 it carries to the next one. KEMYAM has 8 inputs, so 256 rows. Press Run and watch the paper.

Circuit truth table.

  1. 1611110000|1
  2. 8011110010|1
  3. 14411110001|1
  4. 20811110011|1
  5. 25410111111|0
  6. 25501111111|0
  7. 25611111111|0

Row 256 of 256. P8 lights in 4 of 256 rows.

Every wire is the keys that drew it

You draw a wire in Digi-Test with the numeric keypad. Yasser saved each wire as exactly those keys, one digit per step. Draw one yourself.

08080809090906060600

From a node up to the 7404’s ground pin: 3 up, 3 diagonal, 3 right. 9 bytes for 9 cells. In his file this wire takes 31 bytes: two end points, the pins it joins, then the path and the 00 that ends it.

The same wire as an SVG path: M8 12 v-3 l3 -3 h3

8 is up, 2 is down, 4 left, 6 right, and 7, 9, 1 and 3 are the diagonals. A wire is one end point plus a short string of digits. Drawing it from the file is a loop that moves a pen one step per byte. The idea has a name today, a chain code, and you use it every time you write an SVG path with h, v and l.

One more kind of byte draws a part instead of a step. Add the direction and 4 to 60h, and you get a resistor 7 cells long: 6Ah is a resistor pointing right. They are the small red parts on KEMYAM’s ground line and on the way to P8. All 170 wires in his files, KEMYAM’s 38 among them, land exactly on the pins they belong to.

A new chip is a new file, not new code

Digi-Test knows 28 real chips, from the 7400 to the 7440. None of them is written into the program. Each one is a small file that the program reads, the way a modern app reads its components from JSON.

CHIP LIBRARY one file per chip
1 2 3 out 4 5 6 out 7 0V 8 out 9 10 11 out 12 13 14 +5 7400 Nand2_1 × 4

7400: 4 gates of type Nand2_1, from FIL0.IC.

pingatekindat

100200000

200200100

300400200

401200000

501200100

601400200

700100000

802400200

902200000

1002200100

1103400200

1203200000

1303200100

1400020000

typeNand2_1

GATE FILE FIL0.GAT and friends
Controlling value
Inverts

NAND Nand2_1

02inputs 01outputs 01controlling value 02inverts 00 00second output

His evaluator on all four rows
ABout
001
011
101
110

Pick the 7400, then the 7408, then the 7432. Their pins are byte for byte the same: four gates, inputs on pins 1 and 2, output on pin 3, and so on round the chip. The only thing that changes is the gate type written at the end of the file. To teach Digi-Test a new chip, you add 138 bytes. You never touch the code.

The gate types are files too, and two of their bytes decide everything. The controlling value is the input that settles a gate by itself. A 0 settles an AND and a 1 settles an OR, just like false && x and true || x in JavaScript. The second byte says whether to flip the answer. Flip both in the gate file above, and one routine becomes AND, NAND, OR or NOR.

Five values find a broken pin

To test a board, you need inputs that make a broken pin show at the output. Digi-Test finds them by giving every wire five possible values instead of two.

  1. 0low
  2. 1high
  3. Xnot known yet
  4. D1 if the chip works, 0 if it is broken
  5. D̄0 if it works, 1 if it is broken

With D and D̄, one pass simulates the working board and the broken board at the same time. It is like running both branches of an A/B test in one go. This is the heart of J. Paul Roth’s D-algorithm, the method behind the tools chip makers still use to test their chips. Yasser’s gate routine is short. It reads the inputs one by one and counts them by kind. Set up a gate and step through it.

1 D D
  1. 0 decide nothing
  2. 0 D
  3. 0 D̄
  4. 0 X
MAIN.EXE his gate evaluator
  1. 016Amov al,es:[bx+18h]; controlling value, from the .GAT file
  2. 016Fcall far 0D8D:047A; NOT: flip it
  3. 0175mov [bp-7],al; keep the value that decides nothing
  4. 018Bnext:mov al,es:[bx+si]; read one input
  5. 0198cmp al,[bp-8]; the controlling value?
  6. 019Dmov byte ptr [bp-2],1; yes: decided
  7. 01A6cmp al,[bp-7]; the other value?
  8. 01ABinc byte ptr [bp-4]; count it
  9. 01B5cmp ax,6; 6 is his code for D
  10. 01BAcmp ax,9; 9 is his code for D̄
  11. 01C1inc byte ptr [bp-5]; count a D
  12. 01C6inc byte ptr [bp-6]; count a D̄
  13. 01CBinc byte ptr [bp-3]; anything else: an X
  14. 01CEcmp byte ptr [bp-5],0; a D and a D̄?
  15. 01DAmov byte ptr [bp-2],1; then decided too
  16. 01DEinc byte ptr [bp-1]; next input
  17. 01E8cmp al,[bp-1]; any left? go to next
  18. 01FFdone:mov al,es:[bx+18h]; decided: the controlling value
  19. 0214mov al,[bp-7]; all agreed: the other value
  20. 022Dmov byte ptr [bp-8],6; only D left: D (0233: D̄)
  21. 0258or al,1; an X with a fault: try here later
  22. 0270call far 0EFF:0330; invert, if this gate inverts
The same in C
  1. ctrl = gate->ctrl;// 0: AND, 1: OR
  2. other = NOT(ctrl);
  3. decided = agree = nD = nDb = nX = 0;
  4. for (i = 0; i < n && !decided; i++) {
  5. v = in[i];
  6. if (v == ctrl) decided = 1;
  7. else if (v == other) agree++;
  8. else if (v == D) nD++;
  9. else if (v == DBAR) nDb++;
  10. else nX++;
  11. if (nD && nDb) decided = 1;
  12. }
  13. if (decided) out = ctrl;
  14. else if (agree == n) out = other;
  15. else if (!nX) out = nD ? D : DBAR;
  16. else { frontier = 1; return; }
  17. out = invert(out, gate->inv);
Watches
ctl
0 the value that decides it alone (AND family)
i
1 which input
agr
1 inputs at 1, the value that decides nothing
D
1, 0 inputs at D, at D̄
X
0 inputs not known yet
out
D the output
Help

This gate does not invert: the output stays D.

Break a pin on KEMYAM

Now the same routine runs on every gate of the defense circuit. Pick a pin to break, stuck at 0 or stuck at 1, and watch the red D travel toward P8. When P8 shows D or D̄, the working and the broken circuit disagree: these inputs are a test pattern for that fault.

7411 7404 7408 7432 P1 P2 P3 P4 VCC P6 P7 P8 P9 P10 GND

P8 = 1

Inputs
Break
Stuck at

Pick a pin to break.

His ANALYZE menu takes it from there. It builds a fault dictionary: for every pattern, which broken pin changes which output. It lists the faults no pattern can reveal. Then it runs DETECT FAULT through the card, on the real board.

The card, and a probe that knows where to go

With the team’s plug-in card in the PC, Digi-Test tested the real circuit on the bench. Yasser wrote the few lines that talk to the card in assembly, right inside his C++.

the test pattern: flip any bit

Decimal 15 Hex 0Fh

MAIN.EXE the card routine
  1. 0B23mov ax,es:[bx]; the test pattern
  2. 0B2Cmov ah,0; keep 8 bits, one per input
  3. 0B2Emov cx,8; his inline assembly starts here
  4. 0B31xor bx,bx; BX = 0: the reversed byte
  5. 0B33rev:shr ax,1; lowest bit out, into the carry flag
  6. 0B35rcl bx,1; carry in, at the other end
  7. 0B37loop 0B33; 8 times
  8. 0B39mov ax,bx
  9. 0B3Bmov dx,301h; the card
  10. 0B3Eout dx,al; 8 bits to the circuit’s inputs
  11. 0B3Fxchg al,ah
  12. 0B41shr ax,1
  13. 0B43mov dx,300h
  14. 0B46out dx,al; a second write, to port 300h
  15. 0B4Bread:mov dx,300h
  16. 0B4Ein al,dx; read the circuit’s outputs
  17. 0B52inc si / cmp si,7D0h; 2,000 times
  18. 0B5Fmov es:[bx],ax; keep the last reading
The same in C
  1. unsigned char p = pattern;// a bit per input
  2. unsigned char r = 0;
  3. for (i = 0; i < 8; i++) {
  4. carry = p & 1; p >>= 1;// shr
  5. r = (r << 1) | carry;// rcl
  6. }
  7. outportb(0x301, r);// set the inputs
  8. outportb(0x300, r << 7);
  9. for (i = 0; i < 2000; i++)// let it settle
  10. answer = inportb(0x300);
Watches
AX
0000 0000 the pattern, losing a bit each turn
BX
1111 0000 the same bits, reversed
CF
0 the carry flag: one bit in transit
CX
0 turns left
Help

That last reading is the circuit’s answer. Digi-Test compares it with what its simulation expects. If they differ, the guided probe takes over.

The card sits in one of the PC’s expansion slots, at two I/O ports that IBM set aside for home-made cards. There is no driver. One OUT instruction sets the circuit’s inputs and one IN reads its outputs. On an Arduino plugged into USB, the same job is digitalWrite() and digitalRead(). A second routine reads the hand probe the same way, from a single bit.

Place the guided probe here

When the board’s answer differs from the simulation, Digi-Test does more than flag the board. It walks back from the wrong output, one gate at a time, and tells the technician where to touch the guided probe next. Those are his words on the screen. Below, one chip on KEMYAM hides a fault. Apply the pattern, then follow the probe.

7411 7404 7408 7432 P1 P2 P3 P4 VCC P6 P7 P8 P9 P10 GND

Press Apply to send the test pattern.

    Then and now
    1991 to 19932026Why it is the same idea
    Five values, D and D̄, carry one fault through the circuit ATPG tools such as Synopsys TestMAX and Siemens Tessent The same stuck-at faults and the same goal: inputs that make a fault show. Today they run on chips with millions of gates.
    OUT to the card, then IN, straight from the program An Arduino on USB: digitalWrite(), delay(), digitalRead() The same three steps, on a board with no slot.
    “Place the guided probe here” Boundary scan (JTAG) reads the pins from software Walk back from the bad output until a part turns good inputs into a bad output.

    Real C++, in 1993

    C++ was young in 1993, and most DOS programs were plain C. Digi-Test is built from about 20 classes: gates, chips, wires, nodes, inputs and outputs, the drawing sheet, each one carrying its own data and its own code.

    MAIN.EXE one virtual call
    1. 01F2push [bp-0Ch] / push [bp-0Eh]; this: the object, as a far pointer
    2. 01F8les bx,[bp-0Eh]; point at the object
    3. 01FBmov bx,es:[bx]; its first 2 bytes: where its class’s table is
    4. 01FEcall far [bx+34h]; slot 34h of that table

    In C++obj->evaluate();In JavaScriptobj.evaluate();

    A pointer leads to an object; the object's first 2 bytes lead to its class's table; slot 34h of the table leads to the method that runs. pointerthis Gate its data 28 bytes in all 0335h the class’s table 00h04h…30h34h0EFF:012038h… runs 0EFF:0120 A pointer leads to an object; the object's first 2 bytes lead to its class's table; slot 34h of the table leads to the method that runs. pointerthis Gate its data 28 bytes in all 0335h the class’s table 00h04h…30h34h0EFF:012038h… runs 0EFF:0120

    One fixed slot of that table holds the address of this class’s own method. One instruction calls it. For a gate, that is Gate’s own evaluate: the routine you stepped through above.

    This is the idea that makes the whole program hang together. His fault code holds an object and calls one slot of its method table, 15 times in all. It never asks what kind of object it is. A gate answers with gate code, a chip with chip code, a node with node code. In TypeScript you would write an interface with an evaluate() method; in JavaScript, the prototype chain finds the right function the same way.

    He built on Borland C++ and its container classes, which he compiled from their source right inside his project. The biggest object of all is the drawing sheet, 30,105 bytes. Almost all of it is a grid of 153 by 98 cells, one for every spot where a chip, a node or a wire can go.

    For engineers: a constructor, as Borland C++ 2.0 compiled it
    MAIN.EXE 0EFF:0090, the Gate constructor
    00A1 or ax,[bp+8] ; this == 0? then no memory was given
    00A8 push 1Ch ; ask for 28 bytes
    00AA call far 1EAC:005F ; Borland’s Object::operator new
    00D4 call far 0EFF:0ABB ; the parent class’s constructor
    00E5 mov word ptr es:[bx],0335h ; write the Gate method table into the first 2 bytes
    Every class constructor follows this pattern, so following the calls rebuilds his class tree. Slot 34h of the table holds 0EFF:0120 for a gate, 0EFF:06C7 for a chip and 1485:0AA7 for a simulation node.

    The program uses Borland’s large memory model, so every pointer is a 4-byte far pointer and every method call is a far call. Stack checks are on in every one of his functions, a safety net he chose while developing. See the constructor in the byte map, or the gate evaluator.

    His group’s name on every file

    Every file Digi-Test saves starts with the same line, “Copyright 1993 by KEMYAM”, then a Ctrl-Z, then the data, then the file’s own length. Here is a whole gate file, all 60 bytes.

    FIL0.GAT 60 bytes

    The gate: 2 inputs, 1 output, controlling value code 1 (a 0), inverts code 2 (yes). That is a NAND.

    C:\DIGI_T
    C:\DIGI_T>TYPE FIL0.GAT
    Copyright 1993 by KEMYAM (c) For Gate Save
    C:\DIGI_T>_

    The Ctrl-Z is a quiet touch. DOS’s TYPE command stops at that byte, so anyone peeking at a Digi-Test file sees the group’s copyright and nothing else. PNG files use the same trick in their first 8 bytes. The length at the end works like an HTTP Content-Length: a file that was cut short on a floppy is refused at once.

    Files have numbers, not names. A catalog such as GROUP.CIR keeps the names in order, so KEMYAM, the first entry, lives in FIL0.CIR. Before every save, Digi-Test renames the old file to BFL0.CIR, a one-step undo like a .bak file. 18 of those backups are still in the folder.

    For engineers: the five file formats
    • GROUP.* (4 catalogs): from 32h, a count, then one 13-byte entry per file: a 12-byte name and a flag. Entry n is FILn.
    • .GAT (9 gate types): from 32h, inputs, outputs, controlling value, inversion, and 2 bytes for a second output. Values use his codes: 1 means 0, 2 means 1.
    • .IC (28 chips, 7400 to 7440): from 3Ch, the pin count, then 4 bytes per pin (gate number; kind: input 20h, output 40h, ground 10h, power 02h; place in the gate), then the gate type’s name and the number of gates in the package.
    • .TCK (2 logic families, TTL and TTL with open-collector outputs): one byte marks the open-collector family.
    • .CIR (circuits): from 3Ch, an object count, then each object: x, y, a type (0 chip, 2 wire, 3 node, 4 input or output) and its fields. Wires come last, each with its two ends, the pins it joins, and its keypad path.

    The program writes the circuit’s objects with a raw DOS call in inline assembly (1C5C:003C) and checks the length on every load (1C5C:04E5). A decoder built from his save code reads all 70 data files to the exact byte. See the save routine in the byte map.

    Every Byte of MAIN.EXE

    Each square is one byte of the program in memory, colored by what it is. Tap a part to open it in the full map, down to the single instruction.

    C:\DIGI_T\MAIN.EXE

    79,358 bytes on the disk, packed by DIET

    202,720 bytes in memory, 135,915 of them his own code

    • Code66%
    • Borland library21%
    • Fix-up list8.3%
    • Data3.0%
    • Text2.1%
    • Start-up code0.3%
    • Header<0.1%
    • Empty space<0.1%

    The biggest parts, his first

    1. Analysis engine20,704 bytesYasser
    2. Netlist builder and shared helpers16,192 bytesYasser
    3. Chip symbol drawing9,456 bytesYasser
    4. Circuit propagation helpers8,864 bytesYasser
    5. Dialogs and windows8,192 bytesYasser
    6. Main window and menus8,080 bytesYasser

    Mapped by hand from the program’s own bytes.

    Open in the byte mapSee the packed file