CM 59401M


FPGA HP-IB Parity Generator

While connecting the HP-85 to the HP 7970 tape drive, I discovered that the HP-85 interface does not generate parity correctly. But the HP 7970E requires and checks for odd parity, and worse, will hang up the bus if it detects an even parity command word. I first worked around it by sending pre-calculated raw command codes with the correct parity, instead of using the HP-85 baked-in command mnemonics. But this only works if the HP 7970 is the only device on the bus. For the ASCII art demo video, I also had the printer on the bus. The HP-IB printer commands are automatically generated by the HP-85 internal ROM, don't ahve the correct parity, and will cause the HP 7970 to hang the bus. So I instead fixed the problem once and for all by building a simple FPGA-based device that corrects parity on the fly.

Meet the CM 59401M

The Curious - Marc CM 59401M instrument is inspired by the Hewlett-Packard HP 59401A, an early HP-IB logic analyzer instrument that displays or control HP-IB bus data and control lines. You can see how the original HP 59401A works in this video here, it's a lot of fun to play with paddling bits one by one.

My CuriousMarc (CM™) version of device also shows the codes and status lines, but will correct the bus parity on the fly so it is always odd during commands. It will also show which current code is on the HP-IB data lines on the numeric display, either in Octal (if switch 8 on the board is on) or hexadecimal (switch 8 off). When the CM59401 has to intervene to fix parity, dots are illuminated at the bottom of the display. In addition, the LEDs show the state of the HPIB status lines. LEDs are on when lines are active (i.e. low).

D1   D2    D3   D4    D5    D6    D7    D8

It is built from a Numato Lab Elbert v2 FPGA board, which has a Spartan 3A Xilinx chip on-board. The documentation for the Elbert V2 board and project is shown below.

The board is connected to a GPIB connector female. I used a vintage HP 10342 HPIB probe board tucked underneath to make a clean interface, using it as a breakout.

The GPIB pins connect to the Elbert V2 connectors P1 and P6 according to the diagram below

I had to swap connections from P1 to P6 to use the DAV as clock, and P6 is the connector with the FPGA clock inputs. Trust the blue numbers on top of the connectors. The pins of the FPGA board are named in black, while the corresponding GPIB signals from the breakout board are in blue.

The FPGA image and the download tools are below. The USB drivers needs to be installed to connect to the board, then use the ElbertV2Config tool to upload the hpib_paritygen.bin binary to the board.

The source code and project settings for the FPGA project is in the folder below. I used Xilinx ISE Design Suite as the development environment. The project file is the .xise file. There are three sources files for the code itself:

  • clock_gen.v: the 100 MHz clock, auto-generated by the Xilinx macro
  • SevenSegmentDisplay.v: the driver for the 3 digit display
  • HPIB_ParityGen_Top05.v: the actual parity generation code
  • The .pdf files are my handwritten notes taken during the program development, just for info.

The .ucf file is the IO mapping. The .html files are for information, with the project settings and synthesis results summary.

Below is the main code for the parity generator. Parity is changed by pulling the D8 bit line low (i.e. active, as all HP-IB lines are active low). The magic line is:

 assign Parity = ^hpib_data[6:0] || ~EOI || ATN;

which simply keeps calculating parity correction on the fly using simple combinatorial logic. Mixing in EOI and ATN signals makes sure that parity correction only happens when HP-IB commands are sent, but not on data words which can be any 8 bit combination.

`timescale 1ns / 1ps//////////////////////////////////////////////////////////////////////////////////// Company: // Engineer: // // Create Date: 01:46:37 01/13/2016 // Design Name: // Module Name: HPIB_ParityGen_Top // Project Name: // Target Devices: // Tool versions: // Description: //// Dependencies: //// Revision: // Revision 0.01 - File Created// Additional Comments: ////////////////////////////////////////////////////////////////////////////////////module HPIB_ParityGen_Top( input Clk, // hardware clock pin input DPSwitch8, // hardware switch (active low) input [6:0] DIO, // HP-IB data lines 1 t0 7 input DAV, // HP-IB Control lines input NRFD, input SRQ, input NDAC, input REN, input IFC, input ATN, input EOI, output Parity, // HP-IB data line 8, Parity output [7:0] SevenSegment, // hardware LED segments (active low) output [2:0] Enable, // hardware LED anodes enable (active low) output [7:0] LED // active high? ); // Master clock module: 100 MHz clock wire clk_100Mhz; clock_gen c1 (.CLK_IN(Clk), .RST_IN(1'b0), .CLK_OUT(clk_100Mhz));
reg [3:0] digit0, digit1, digit2; reg [2:0] dot, blank; reg parity_reg; wire [11:0] hpib_data; // internal bus to connect IO signals from input
// connect LEDs directly to the HP-IB lines with an inverter // LED are active high, HP-IB control lines are active low // LED are reverse numbered on the board, LED[0] is D8 // D1 D2 D3 D4 D5 D6 D7 D8 assign LED = {~DAV, ~NRFD, ~SRQ, ~NDAC, ~REN, ~IFC, ~ATN, ~EOI}; // connect inverted HP-IB imput data to lower bits of hpib_data assign hpib_data = {5'b00000, ~DIO[6:0]}; // connect the Parity output to a 3-state register //assign Parity = parity_reg; initial begin parity_reg=1'bz; // initial parity is high-z dot=3'b000; end // splice hpib-data bits into digits for hex or octal readout always @(*) begin if(DPSwitch8) begin // switch 8 off/high, display in hex digit0 = hpib_data[3:0]; digit1 = hpib_data[7:4]; digit2 = hpib_data[11:8]; blank = 3'b100; // leftmost digit blank end else begin // switch 8 on/low, display in octal digit0 = {1'b0,hpib_data[2:0]}; digit1 = {1'b0,hpib_data[5:3]}; digit2 = {1'b0,hpib_data[8:6]}; blank = 3'b000; // all digits on end end
// generate parity continously by combitional logic assign Parity = ^hpib_data[6:0] || ~EOI || ATN; // show state of parity generation at DAV always @(negedge DAV) begin if(Parity) dot <= 3'b000; else dot <= 3'b111; end // display result on 7-segment onboard display SevenSegmentDisplay display(clk_100Mhz, digit0, digit1, digit2, dot, blank, SevenSegment, Enable);