In seeking to improve my ISA POST card design, I became frustrated at the general lack of modern seven segment display decoders that could decode and display a hexadecimal output. Rather than buy an older second-hand decoder IC, I fell down the rabbit hole of programming a PLD device to accomplish the task.
For the TL:DR crowd, See my Hex_To_Seven_Segment_Decoder repository on Github for the compiled “.jed” file and WinCUPL project files.
Continue reading for some lengthy background to the problem and a derivation of the PLD code.
What is a seven segment display decoder?
To take an initial step back and gain some perspective; a seven segment display is typically an assembly of eight LEDs, arranged in a numeral shaped like the number eight, with a decimal point thrown in for good measure. Each LED in the numeral can be turned on or off in some kind of pattern to simulate human-readable numbers.
Because there are seven LEDs in the numeral, you would need seven individual inputs to define the pattern of LEDs correctly. If you were driving this display directly with a microcontroller, you would need seven corresponding outputs on the micro. This is a pretty big number of outputs just to display a single digit. It gets worse; very specific drive patterns need to be achieved in order to activate the correct LEDs and properly render human-readable digits.
This is where the seven segment display decoder comes in; it is a chip that is designed to convert a BCD input into an output capable of driving a 7 segment display, including all of the internal logic to select the correct patterns of the LEDs. This chip saves a lot of time by handling the conversion internally, and reduces the number of microcontroller outputs required to drive a seven segment display.
Common display decoder ICs
As it turns out, there are many seven segment display decoders that will convert a BCD input and display a decimal number. The 74LS47 chip is perhaps the oldest workhorse of them all. This chip contains all of the internal circuitry required to convert a BCD signal into a decimal number that appears on an attached seven segment display.
There is one big catch to using the 74LS47. Everything will work as expected as long as the BCD input ranges in value between zero and nine. If the BCD input isn’t in that range, then strange things happen:
For any BCD value higher than nine, the displayed digit results in a garbled mess; If we feed the input a binary value of 14, then the display will show a weird “t” sort of shape.
Why this is a problem for a POST card
Ordinarily the out-of-bounds BCD conversion issues would not be a problem, but for the purposes of an ISA POST card this makes things very inflexible.
The way that the POST card operates is that it listens for any values written to a particular address or “port”. Once it captures the written value (an eight-bit number) it splits it in two, and then feeds the “upper” and “lower” four-bit halves into two separate 74LS47 chips driving two separate displays. If any part of the diagnostic code is outside of the BCD range, the POST card will display the garbled mess as above. More to the point; if any diagnostic codes are in a native hexadecimal format, then the POST card will not be able to display the hexadecimal letters.
Imagine some hypothetical BIOS throws the diagnostic code “3B” to be captured by the POST card. This could be a completely legitimate code signifying something important. If two 74LS47 chips are used in the POST card, then the displayed value will be:
Which is obviously not “3B” in human-readable terms. It might be possible to work backwards from the weird “c” shape and determine what the code should have been but this it not convenient at all. Unfortunately there are very few microchips that will convert a hexadecimal input value range and output hexadecimal symbols on a seven segment display. Some legacy chips do exist, for example: MC14495, DM8880, and DM9368, but none of these chips appear to be commercially available any longer.
Implementing in a PLD
A Programmable Logic Device (PLD) may be an excellent candidate for a replacement chip that will mimic the behaviour of the legacy display decoder ICs that are no longer commercially available. A PLD is a kind of “rudimentary FPGA” in the sense that it contains a network of a few dozen logic gate elements that can be linked together between input pins and output pins.
If the PLD is programmed in a certain way, it could replicate all of the internal logic that converts a four-bit input word and displays a hexadecimal value on an attached seven-segment display.
In my particular instance, I wanted to mimic the venerable 74LS47 logic chip as closely as possible. The idea being that trace-routing would be greatly simplified, and allow for easier accommodation of the two different chip variations. With any luck, the PLD device could maybe behave as a drop-in replacement to the 74LS47 chip.
As it turns out, there are very limited choices when it comes to PLDs. If I want to mimic the 74LS47 then I would need to choose a through-hole part, and if I want to be able to re-program the PLD then I am limited to a type of “EEPLD” or Electronically Erasable Programmable Logic Device. After filtering these requirements, the only choices appear to be:
- ATF16V8B (In a PDIP-20 package)
- ATF22V10C (In a PDIP-24 package)
Of these two choices, the ATF16V8B is the more attractive, with a pin-count closest to that of the 74LS47, which is PDIP-16. Straight away, the idea of having a drop-in replacement gets much more difficult.
Mapping PLD pins
Fortunately, almost every pin of the ATF16V8B is available for mapping. With some creative alignment of pins, the 74LS47 can be mimicked fairly well:
- Pin 1 of the ATF16V8B is skipped as it would not exist on the 74LS47.
- Pin 2 to Pin 8 of the ATF16V8B are mapped according to the range Pin 1 to Pin 7 of the 74LS47 (LT, BI and RBI are not implemented to simplify the background logic and keep code size low).
- Pin 9 of the ATF16V8B is skipped as it would be the ground pin of the 74LS47.
- Pin 10 of the ATF16V8B is the actual ground pin of the ATF16V8B.
- Pin 11 of the ATF16V8B is skipped as it would not exist on the 74LS47.
- Pin 12 to Pin 18 of the ATF16V8B are mapped according to the range Pin 9 to Pin 15 of the 74LS47.
- Pin 19 of the ATF16V8B is skipped as it would be the power pin of the 74LS47.
- Pin 20 of the ATF16V8B is the actual power pin of the ATF16V8B.
Although this is not a perfect replication of the 74LS47 pin layout, it’s about as close as it can be.
A “kind of” drop-in replacement
In theory, the ATF16V8B could be modified to act as a direct drop-in replacement to the 74LS47:
- The slender downward-facing portions of the legs of Pin 1, Pin 10, Pin 11 and Pin 20 could be clipped off, leaving a small stub of the pin leg protruding from the IC case.
- Pin 9 and Pin 10 could be bridged with solder or wire to move the location of the ground-pin to the Pin 9 position.
- Pin 19 and 20 could be bridged with solder or wire to move the location of the power-pin to the Pin 19 position.
Shorting pins intuitively seems like a bad idea, but this strategy appears like it could work based on the diagrams in the datasheet of the ATF16V8B.
Shorting Pin 9 to ground does not appear to cause an internal short due to the large pull-up resistor on the input:
Shorting Pin 19 to VCC does not appear to cause an internal short as this pin is already pulled to VCC by a large pull-up resistor on the input/output:
At face value the ATF16V8B appears as though it could be modified from a PDIP-20 package to a PDIP-16 package, and still retain the core layout of the 74LS47 while providing hexadecimal output functionality.
I would make absolutely no guarantees of this working, however, until it is actually tested. It’s also highly likely that modifying the ATF16V8 by clipping off its legs would prevent the chip from ever being programmed again. Not a perfect solution, but we appear to be getting close.
The missing hexadecimal codes
In order to mimic and expand the functionality of the 74LS47 it will be necessary to derive the output codes for the various hexadecimal digits so that they can be programmed into the ATF16V8B.
Thankfully there is a very consistent naming convention for the various discrete LEDs within seven segment displays:
Each hexadecimal symbol that we wish to display can be decomposed into a 7-bit word that describes which of the individual LEDs is on or off. The convention that I’ll use is “0bABCDEFG” i.e. the “A” LED will be in the MSB position and the “G” LED will be in the LSB position. See the below table for a summary of the new hexadecimal display codes:
|Hexadecimal char||Seven segment output code|
|0b1110111 (0x77) (119)|
|0b0011111 (0x1F) (31)|
|0b1001110 (0x4E) (78)|
|0b0111101 (0x3D) (61)|
|0b1001111 (0x4F) (79)|
|0b1000111 (0x47) (71)|
The Input/Output Map
Knowing the mapping of the hexadecimal codes, the full mapping across the entire BCD input range can be summarised:
|BCD Input code||Seven segment output code||Description|
|0b0000 (0x00) (0)||0b1111110 (0x7E) (126)||Symbol “0”|
|0b0001 (0x01) (1)||0b0110000 (0x30) (48)||Symbol “1”|
|0b0010 (0x02) (2)||0b1101101 (0x6D) (109)||Symbol “2”|
|0b0011 (0x03) (3)||0b1111001 (0x79) (121)||Symbol “3”|
|0b0100 (0x04) (4)||0b0110011 (0x33) (51)||Symbol “4”|
|0b0101 (0x05) (5)||0b1011011 (0x5B) (91)||Symbol “5”|
|0b0110 (0x06) (6)||0b1011111 (0x5F) (95)||Symbol “6”|
|0b0111 (0x07) (7)||0b1110000 (0x70) (112)||Symbol “7”|
|0b1000 (0x08) (8)||0b1111111 (0x7F) (127)||Symbol “8”|
|0b1001 (0x09) (9)||0b1110011 (0x73) (115)||Symbol “9”|
|0b1010 (0x0A) (10)||0b1110111 (0x77) (119)||Symbol “A”|
|0b1011 (0x0B) (11)||0b0011111 (0x1F) (31)||Symbol “b”|
|0b1100 (0x0C) (12)||0b1001110 (0x4E) (78)||Symbol “C”|
|0b1101 (0x0D) (13)||0b0111101 (0x3D) (61)||Symbol “d”|
|0b1110 (0x0E) (14)||0b1001111 (0x4F) (79)||Symbol “E”|
|0b1111 (0x0F) (15)||0b1000111 (0x47) (71)||Symbol “F”|
Generating PLD Code
PLDs are programmed by flashing them with a special “JEDEC” file that has a “.jed” file extension. The “.jed” files contain all of the information necessary to set the various internal logic gate configurations and connections. The “.jed” files can be generated by using a program called WinCUPL.
WinCUPL can now be downloaded from the PLD Design Resources page on the Microchip website (I’m pretty sure it used to be an Atmel software but must have been captured in the recent acquisition by Microchip). WinCUPL acts as a sort of IDE and compiler that will convert the specific PLD programming language into the appropriate “.jed” file. I will use WinCUPL to write code and program the ATF16V8B.
The programming language for WinCUPL is a bit strange at first, but easy enough to learn. there are dozens of tutorials online (videos, lecture slides, etc) that can be consulted to figure out how it’s done. Search “WinCUPL tutorial”.
The code presented here has been written loosely following an example by Peter Murray (39K).
The first block of code contains some information about the WinCUPL project, and is auto-generated upon starting a new project in the WinCUPL software:
Name Hex_to_7seg_Decoder ;
PartNo ATF16V8B ;
Date 21/06/2020 ;
Revision 1.2 ;
Designer Andavno ;
Company na ;
Assembly na ;
Location na ;
Device virtual ;
The second block of code is where the output pins are defined; this code links the physical pin number of the ATF16V8B with a variable name that we are free to make up ourselves. The pin numbers match the mapping convention above. I’ve arbitrarily called the inputs “I0”, “I1” etc. Comments are enclosed by “/*” and “*/”. I’ve put comments in to keep track of the BCD bit positions. BCD A is LSB, BCD D is MSB:
/* *************** INPUT PINS *********************/
PIN 8 = I0 ; /* BCD A (LSB)*/
PIN 2 = I1 ; /* BCD B */
PIN 3 = I2 ; /* BCD C */
PIN 7 = I3 ; /* BCD D (MSB)*/
The third block of code is where the output pins are defined; this code links the physical pin number of the ATF16V8B with a variable name that we are free to make up ourselves. The pin numbers match the mapping convention above. I’ve called the outputs “A”, “B”, etc to match the seven segment LED segment names:
/* *************** OUTPUT PINS ******************** */
PIN 16 = A; /* Seven segment LED "A" */
PIN 15 = B; /* Seven segment LED "B" */
PIN 14 = C; /* Seven segment LED "C" */
PIN 13 = D; /* Seven segment LED "D" */
PIN 12 = E; /* Seven segment LED "E" */
PIN 18 = F; /* Seven segment LED "F" */
PIN 17 = G; /* Seven segment LED "G" */
The fourth block of code is where all of the magic happens; this is where the inputs and outputs are mapped and optimised by WinCUPL. The input and the output names are collected into arrays (fields) and then a table is defined in which the exact mapping is specified.
Note that the order of appearance of the variables in the field arrays is important; they must match the appropriate MSB or LSB order as is described in the input/output mapping table.
Another very important note: the 74LS47 that we are mimicking is a Common Anode driver. This means that the outputs are actually natively inverted by the 74LS47 in order to drive common anode displays directly (this is not immediately obvious in the 74LS47 datasheet as the output table values are shown non-inverted. One of the only hints comes from the output column labels having the “not” bar above their symbols). The binary values in the WinCUPL code have been inverted to properly mimic the 74LS47 operating in Common Anode mode:
FIELD INPUT = [I3,I2,I1,I0]; /* input array definition */
FIELD OUTPUT = [A,B,C,D,E,F,G]; /* output array definition */
TABLE INPUT => OUTPUT /* output values inverted because Common Anode */
'b'0000 => 'b'0000001; /* Symbol "0" */
'b'0001 => 'b'1001111; /* Symbol "1" */
'b'0010 => 'b'0010010; /* Symbol "2" */
'b'0011 => 'b'0000110; /* Symbol "3" */
'b'0100 => 'b'1001100; /* Symbol "4" */
'b'0101 => 'b'0100100; /* Symbol "5" */
'b'0110 => 'b'0100000; /* Symbol "6" */
'b'0111 => 'b'0001111; /* Symbol "7" */
'b'1000 => 'b'0000000; /* Symbol "8" */
'b'1001 => 'b'0001100; /* Symbol "9" */
'b'1010 => 'b'0001000; /* Symbol "A" */
'b'1011 => 'b'1100000; /* Symbol "b" */
'b'1100 => 'b'0110001; /* Symbol "C" */
'b'1101 => 'b'1000010; /* Symbol "d" */
'b'1110 => 'b'0110000; /* Symbol "E" */
'b'1111 => 'b'0111000; /* Symbol "F" */
All four segments of code can now be collected together and compiled.
How to Compile the Code
The remainder of this post will act as a sort of how-to guide on compilation of code in WinCUPL. It took me a little while to find out how to do this so I’m adding it to assist others who might be wondering like I was.
WinCUPL can be downloaded from the PLD Design Resources page on the Microchip website. It’s a Windows program that happily installs under Windows 7.
Once installed, begin by selecting File > New > Project from the file menu:
The first window asks for some of the design information that you might want to associate with the new project. The most important fields are probably “Name” and “Date”, but this information is not super critical. You could accept the defaults. WinCUPL is asking this simply to pre-populate the project information like that seen in the first code block above. Click “OK” when done.
The next window asks how many input pins there are on the target IC. The ATF16V8B has 10 input pins so this is what I’ll put in. This number is not super critical; you could put zero. WinCUPL is asking this simply to populate the code template for use later on. Click “OK” when done:
The next window asks how many output pins there are on the target IC. The ATF16V8B has 8 input pins. This number is not super critical; you could put zero. WinCUPL is asking this simply to populate the code template for use later on. Click “OK” when done:
The next window asks how many “pin nodes” there are on the target IC. I put in 18 as I thought this was the sum of the input and output pins. This number is not super critical; you could put zero. WinCUPL is asking this simply to populate the code template for use later on. Click “OK” when done:
WinCUPL then creates the project file which will contain all of our code. The code is presented in the main window. All of the project information and pin inputs have been translated into pre-filled code:
Before writing any new code, it’s important to set up the target device for WinCUPL to compile against. Select Options > Devices from the file menu:
A window pops up with various target IC options across three columns. In our case, we select the ATF16V8B. We also make sure to un-tick “Device In File”. Click “OK” when done:
Another important setup step is to make sure that WinCUPL will generate the correct file types that we require (the “.jed” file). Select Options > Compiler from the file menu:
A “Compiler Options” window will pop up. Select the “Output Files” tab near the top of the window. Make sure “JEDEC” is ticked in the “Download” area. Also make sure that the “Fuse Plot” and “Equations” options are ticked in the “Doc File Options” area as these files can be very handy to see once the code has been compiled. Don’t click OK just yet:
Select the “General” tab near the top of the window. Make sure that “Simulate” and “JEDEC name = PLD name” are selected in the tab area. Click “Apply” and then “OK”:
Now the compiler is configured and the code can be worked upon in the main project code window.
For this demonstration, select all of the pre-generated code and delete it. Copy in the four code blocks above by pasting them into the code window one after the other.
To compile the code, select Run > Device Dependent Compile from the file menu:
WinCUPL warns that the project has not yet been saved. NOTE: by default, WinCUPL stores the unsaved project file in the root installation directory. The compiled files are generated in the same folder as the project file. If you care about such things, it would be wise to cancel out and save the project file in its own dedicated directory before proceeding. Click “yes” if you’re OK with the project file and compiled outputs appearing in the root installation directory:
The file list on the right hand side of the program window should now update itself to show the final output files, one of which is “NAME.jed” which is the JEDEC file that can be directly programmed into the ATF16V8B:
Now it’s a simple task of navigating to the appropriate project directory, copying and/or renaming the “.jed” file, and then using the “.jed” file to program the ATF16V8B.
All four segments of code can now be collected together and compiled.
Programming the ATF16V8B
For some instruction on how to go about programming the ATF16V8B with a TL866II PLUS programmer, please see “Flashing U16” section of my post on building the Micro 8088.
After programming two ATF16V8B chips with the compiled code, I tested them in my new ISA Post Card v3.0 (a separate post on this new card upcoming).
To do this test properly, I used Sergey Kiselev’s Xi 8088 Processor board which I have constructed in the background (again, another post on this upcoming).
The BIOS for the Xi 8088 uses hexadecimal output post codes, and the new version of the post card (using the ATF16V8B chips) works flawlessly to capture and display the hexadecimal values:
(yes, it has taken ten months to finally document the PLD code and talk about the new post card design)
This long and tedious post has shown the success in programming an ATF16V8B chip to emulate a 74LS47 seven segment display decoder IC with new hex-output functionality.
See my Hex_To_Seven_Segment_Decoder repository on Github for the compiled “.jed” file and WinCUPL project files.
With this post finally out of the way, I intend to talk about my new ISA post card, and release the KiCAD files for the newly designed card.
As always I hope you have found this interesting and I hope you join me again as I document my progress in this wonderful hobby of retro-computing.