Synplify Pro on Linux Mint 18.1: The cheat sheet

Introduction

I needed to run Synplify Pro for a short trial period on my Fedora 12 machine (yup, it’s 2018, and still). And I have a full Mint 18.1 as a chroot jail on that machine for installing contemporary software.

So these are my notes on the go. Consider everything below as run om Mint 18.1 x86_64 (which is an Ubuntu derivative), except for the licensing manager, which I eventually ran directly on the Fedora 12 oldie (x86_64 as well).

I should point out, that Synopsys officially supports only Red Hat based distributions (SUSE and RHEL), which explains why small tweaks were necessary. But once that is over with, all was fine.

Synopsys offers extensive documentation for all this, of course. As the title implies, this should be considered as a cheat sheet, nothing more.

Download the stuff

In essence, three parts are needed: Synopsys’ installer program, the tool to run and the licensing manager. In my case, I downloaded all inside the following directories (into separate directories on my own computer):

  • /rev/installer_v4.1
  • /rev/s_fpga_d_vN-2018.03-SP1
  • /rev/scl_v2018.06

These three directories happened to be everything under /rev/ in my case (this is what they prepared for me, I suppose). So I grabbed it all. This included some large files for Windows, which I surely didn’t need, but it’s easier to fetch all files and wait longer than to use my own brain, for example.

Make the system ready

C-shell is used by the installation scripts (and others, possibly):

# apt-get install csh

Synplify itself expects a LSB (Linux Standard Base), in particular a symlink in /lib64/ for the ELF loader.

# apt-get install lsb-core

Without this, the licensing related program go something like:

$ ./lmhostid
-bash: ./lmhostid: No such file or directory

And then you go “But what??? The file is there!” and you’re right, the file is there, but the ELF loader which the executable requests isn’t, because it’s /lib64/ld-lsb-x86-64.so.3. I’ve discussed this issue in another post.

Also, some scripts have their shebang to /bin/sh (!). Unfortunately, Debian’s standard symlink for /bin/sh goes to /bin/dash (because working out of the box is for the weak). So

# cd /bin
# mv sh old-sh
# ln -s bash sh

If you don’t change this symlink, the typical error goes “synplify-pro-exec/fpga/N-2018.03-SP1/bin/config/execute: Syntax error: “(” unexpected (expecting “;;”)”

Then create another symlink from /usr/tmp to /tmp, because the licensing deamon creates the lock file there. As root:

# cd /usr
# ln -s /tmp

Installing

Refer to the Synopsys’ installation guide for how to run through the installation. This is just a brief.

Installing doesn’t require being root, if the target directories are owned by the user.

First, install the installer. In the directory where the installer was downloaded to, go

$ ./SynopsysInstaller_v4.1.run

And extract the installer into some other directory.

Navigate to the directory to which the installer was installed, and go

$ ./setup.sh

for a GUI installer, or

$ ./installer

for the textual stuff (but the ssh’ers).

The installer should be run (at least) twice: Once to install the tool of interest, and a second time to install the licensing manager.

For each time tell the installer where Synopsys’ installation files were downloaded to, and then to where the installed program should go. Both are different directories for each of the two installations.

Editing the licensing file

Edit the SERVER line, replacing “hostname1″ with the actual host name (as returned by “uname -n”).

There is no need to change the VENDOR line. At least in my case, it worked fine as is.

Check the licensing file

Be sure it’s valid. Navigate to where the licensing manager was installed (e.g. scl/2018.06/linux64/bin), and go (below is a successful validation for a temporary key):

$ ./sssverify /path/to/license.txt 

Integrity check report for license file "/path/to/license.txt".
Report generated on 06-Jul-2018 (SCL_2018.06)
---------------------------------------------------------
Checking the integrity of the license file...
Valid SSST feature found.
Licensed to Temp Keys for New Customers
Siteid: 5.1, Server Hostid: 200247EDD334, Issued on: 7/4/2018
License file integrity check PASSED!
---------------------------------------------------------
You may now USE this license file to start your license server.
Please don't edit or manipulate the contents of this license file.

Or use the -pinfo flag for a list of licensed features:

$ ./sssverify -pinfo /path/to/license.txt
=============================================================================================
	PRODUCT TO FEATURE MAPPING REPORT GENERATED ON 6/7/2018
---------------------------------------------------------------------------------------------
License File: /path/to/license.txt
Site ID: NEWSITE
Host ID: 200247EDD334
Key File Date: 07/04/2018
SCL Version: SCL_2018.06
=============================================================================================

=============================================================================================
Product: *****			 Serial Number: (SN=0:0)
---------------------------------------------------------------------------------------------
Feature Name                     Expiry-Date  Daemon       Version Quantity        Start-Date
---------------------------------------------------------------------------------------------
SSST                             22-Jul-2018  snpslmd      1.0            1       04-Jul-2018
=============================================================================================

=============================================================================================
Product: *****			 Serial Number: (SN=4881-0:503161)
---------------------------------------------------------------------------------------------
Feature Name                     Expiry-Date  Daemon       Version Quantity        Start-Date
---------------------------------------------------------------------------------------------
synplifypro_altera               22-jul-2018  snpslmd      2018.03        1
=============================================================================================

Start the licensing manager

OK, this is the only place where I left my Mint chroot jail, because I got

18:07:13 (snpslmd) Cannot open daemon lock file
18:07:13 (snpslmd) EXITING DUE TO SIGNAL 41 Exit reason 9
18:07:13 (lmgrd) snpslmd exited with status 41 (Exited because another server was running)

and then it just worked on Fedora 12, so what the heck with that. There is a word that the licensing manager doesn’t work on reiserfs, and I also spotted with strace that this failure occurs immediately after getdents() system calls on the root directory, which was a fake root in my case. So maybe because of that, maybe something else I didn’t get right, or more precisely: Didn’t bother to get right.

Anyhow, root aren’t required, and neither is any environment variable.

$ cd /path/to/scl/2018.06/linux64/bin
$ ./lmgrd -c /path/to/license.txt

And of course, if you’re really into it, make a service for this on your machine.

Is there a licensing manager running?

Is it up?

 ./lmstat
lmstat - Copyright (c) 1989-2017 Flexera Software LLC. All Rights Reserved.
Flexible License Manager status on Fri 7/6/2018 20:59

License server status: 27020@myhost.localdomain
    License file(s) on myhost.localdomain: /path/to/license.txt:

myhost.localdomain: license server UP (MASTER) v11.14.1

Vendor daemon status (on myhost.localdomain):

   snpslmd: UP v11.14.1

What’s its process (for killing)?

$ ps aux | grep lmg
eli      27499  0.0  0.0  17760  1428 pts/15   S    17:41   0:00 ./lmgrd -c /path/to/license.txt

Shut down the licensing manager

$ ./lmdown -c /path/to/synplify-pro-exec/license.txt

This works however only if the licensing manager went up OK. Otherwise, it might say it shut down the daemon, but there’s still a process running.

Running Simplify Pro

Finally there.

Be sure that the licensing manager is up and running, and go:

$ SNPSLMD_LICENSE_FILE='27020@localhost' ./synplify_pro &

Synopsys’ docs tell us to set and export the environment variable, but this way works, and this is how I like it.

Cyclone V and some transceiver CDR/PLL parameters

Introduction

Connecting an Intel FPGA (Altera) Cyclone V’s Native Transceiver IP to a USB 3.0 channel (which involves a -5000 ppm Spread Spectrum modulation), I got a significant bit error rate and what appeared to be occasional losses of lock. Suspecting that the CDR didn’t catch up with the frequency modulation, I wanted to try out a larger PLL bandwidth = track more aggressively at the expense of higher jitter. That turned out to be not so trivial.

This post sums up my findings related to Quartus. As for solving the original problem (bit errors and that), changing the bandwidth made no difference.

Toolset: Quartus Lite 15.1 on Linux.

And by the way, the problem turned out to be unrelated to the PLL, but the lack of an  equalizer on Cyclone V’s receiver. Hence no canceling of the low-pass filtering effect of the USB 3.0 cable. I worked this around by setting XCVR_RX_LINEAR_EQUALIZER_CONTROL  to 2 in the QSF file and the errors were gone. However this just activates a constant compensation high-pass filter on the receiver’s input (see the Cyclone V Device Datasheet, CV-51002,  2018.05.07, Figure 4) and consequently works the problem around for a specific cable, not more.

Assignments in the QSF file

In order to change the CDR’s bandwidth, assignments in the QSF are due, as detailed in V-Series Transceiver PHY IP Core User Guide (UG-01080, 2017.07.06) in the section “Analog Settings for Cyclone V Devices” and on page 20-28. In principle, CDR_BANDWIDTH_PRESET should be set to High instead of its default “Auto”. In this post, I’ll also set PLL_BANDWIDTH_PRESET to High, even though I’m quite confident it has nothing to do with locking to data (rather, it controls locking to the reference clock). But it causes quite some confusion, as shown below.

So all that is left is to nail down the CDR’s instance name, and assign it these parameters.

Now first, what not to do: Using wildcards. This is quite tempting because the path to the CDR is very long. So at first, I went for this, which is wrong:

set_instance_assignment -name CDR_BANDWIDTH_PRESET High -to *|xcvr_inst|*rx_pma.rx_cdr
set_instance_assignment -name PLL_BANDWIDTH_PRESET High -to *|xcvr_inst|*rx_pma.rx_cdr

And nothing happened, except a small notice in some very important place of the fitter report:

+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
; Ignored Assignments                                                                                                                                                                                   ;
+--------------------------------------------------+---------------------------+--------------+------------------------------------------------------------+---------------+----------------------------+
; Name                                             ; Ignored Entity            ; Ignored From ; Ignored To                                                 ; Ignored Value ; Ignored Source             ;
+--------------------------------------------------+---------------------------+--------------+------------------------------------------------------------+---------------+----------------------------+
; Merge TX PLL driven by registers with same clear ; altera_xcvr_reset_control ;              ; alt_xcvr_reset_counter:g_pll.counter_pll_powerdown|r_reset ; ON            ; Compiler or HDL Assignment ;
; CDR Bandwidth Preset                             ; myproj                    ;              ; *|xcvr_inst|*rx_pma.rx_cdr                                 ; HIGH          ; QSF Assignment             ;
; PLL Bandwidth Preset                             ; myproj                    ;              ; *|xcvr_inst|*rx_pma.rx_cdr                                 ; HIGH          ; QSF Assignment             ;
+--------------------------------------------------+---------------------------+--------------+------------------------------------------------------------+---------------+----------------------------+

Ayeee. So it seems like there’s no choice but to spell out the entire path. I haven’t investigated this thoroughly, though. Maybe there is some form of wildcards that would work. I also discuss this topic briefly in another post of mine.

So this is more like it:

set_instance_assignment -name CDR_BANDWIDTH_PRESET High -to frontend_ins|xcvr_inst|xcvr_inst|gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|inst_av_pma|av_rx_pma|rx_pmas[0].rx_pma.rx_cdr
set_instance_assignment -name PLL_BANDWIDTH_PRESET High -to frontend_ins|xcvr_inst|xcvr_inst|gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|inst_av_pma|av_rx_pma|rx_pmas[0].rx_pma.rx_cdr

I guess this clarifies why wildcards are tempting.

Verifying something happened

This is where things get confusing. Looking at the fitter report, in the part on transceivers, this was the output before adding the QSF assignments above (pardon the wide line, this is what the Fitter produced):

;         -- Name                                                                                           ; frontend:frontend_ins|xcvr:xcvr_inst|altera_xcvr_native_av:xcvr_inst|av_xcvr_native:gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|av_pma:inst_av_pma|av_rx_pma:av_rx_pma|rx_pmas[0].rx_pma.rx_cdr                                                                                                                                                                                                                                                     ;
;         -- PLL Location                                                                                   ; CHANNELPLL_X0_Y49_N32                                                                                                                                                                                                                                                                                                                                                                                                                                                                       ;
;         -- PLL Type                                                                                       ; CDR PLL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     ;
;         -- PLL Bandwidth Type                                                                             ; Auto (Medium)                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ;
;         -- PLL Bandwidth Range                                                                            ; 2 to 4 MHz

And after adding the QSF assignments:

;         -- Name                                                                                           ; frontend:frontend_ins|xcvr:xcvr_inst|altera_xcvr_native_av:xcvr_inst|av_xcvr_native:gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|av_pma:inst_av_pma|av_rx_pma:av_rx_pma|rx_pmas[0].rx_pma.rx_cdr                                                                                                                                                                                                                                                     ;
;         -- PLL Location                                                                                   ; CHANNELPLL_X0_Y49_N32                                                                                                                                                                                                                                                                                                                                                                                                                                                                       ;
;         -- PLL Type                                                                                       ; CDR PLL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     ;
;         -- PLL Bandwidth Type                                                                             ; High                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        ;
;         -- PLL Bandwidth Range                                                                            ; 4 to 8 MHz

Bingo, huh? Well, not really. Which of these two assignments made this happen? CDR_BANDWIDTH_PRESET or PLL_BANDWIDTH_PRESET? In other words: Does the fitter report tell us about the bandwidth of the PLL on the reference clock or the data?

The answer is PLL_BANDWIDTH_PRESET. Setting CDR_BANDWIDTH_PRESET doesn’t change anything in the Fitter report at all. I know it all too well (after spending some pleasant quality time trying to figure out why, before realizing it’s about PLL_BANDWIDTH_PRESET).

So where’s does CDR_BANDWIDTH_PRESET do its trick?

To find that, one needs to get down to the post-fitting properties of the rx_cdr instance. The following sequence applies to Quartus 15.1′s GUI:

After fitting, select Tools > Netlist Viewers > Technology Map Viewer (Post-Fitting). Locate the instance in the Find tab (to the left; it’s a plain substring search on the instance name given in the QSF assignment). Once found, click on the block in the graphics display so its bounding box becomes red, and then right-click this block. On the menu that shows up, select Locate in Resource Property Editor.

And that displays a list of properties (which can be exported into a CSV file). One of which is rxpll_pd_bw_ctrl. Changing CDR_BANDWIDTH_PRESET to High altered this property’s value from 300 to 600. Changing it to Low sets it to 240.

And by the way, a change in PLL_BANDWIDTH_PRESET to High has no impact on any of the properties listed in the Resource Property Editor for the said instance, but making it Low takes pfd_charge_pump_current_ctrl from 30 to 20, and rxpll_pfd_bw_ctrl from 4800 to 3200. Whatever that means.

It’s worth mentioning that the CDR is instantiated as an arriav_channel_pll primitive (yes, an Arria V primitive on a Cyclone V FPGA) in the av_rx_pma.sv module (generated automatically for the Transceiver Native PHY IP). One of the instantiation parameters is rxpll_pd_bw_ctrl, which is assigned 300 by default. The source file doesn’t change as a result of the said change in the QSF file. So the tools somehow change something post-synthesis. I guess.

There are however no instantiation parameters for neither pfd_charge_pump_current_ctrland nor rxpll_pfd_bw_ctrl. So the rxpll_pd_bw_ctrl naming match is probably more of a coincidence. Once again, I guess.

A closer look on the PLL

It’s quite clear from above that CDR_BANDWIDTH_PRESET influenced rxpll_pd_bw_ctrl (note the _pd_ part) and that PLL_BANDWIDTH_PRESET is related to a couple of parameters with pfd them. This terminology goes along with the one used in the documentation (see e.g. Figure 1-17, “Channel PLL Block Diagram” in Cyclone V Device Handbook Volume 2: Transceivers, cv_5v3.pdf, 2016.01.28): The displayed terminology is that PFD relates to the Lock-To-Reference loop, which locks on the reference clock, and PD relates to the Lock-To-Data loop, which is the CDR.

This isn’t just a curiosity, because the VCO’s output dividers, L, are assigned separately for the PD and PDF loops (see the fitter report as well as Table 1-9).

As for the numbers in the fitter report, they match the doc’s as shown in the two relevant segments below. The first relates to a Native PHY IP, and the second to a PCIe PHY, both on the same design, both targeted at 5 Gb/s (and hence having the same “Output Clock Frequency”).

;         -- Reference Clock Frequency                                                                      ; 100.0 MHz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ;
;         -- Output Clock Frequency                                                                         ; 2500.0 MHz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  ;
;         -- L Counter PD Clock Disable                                                                     ; Off                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         ;
;         -- M Counter                                                                                      ; 25                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          ;
;         -- PCIE Frequency Control                                                                         ; pcie_100mhz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ;
;         -- PD L Counter                                                                                   ; 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;
;         -- PFD L Counter                                                                                  ; 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;
;         -- Powerdown                                                                                      ; Off                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         ;
;         -- Reference Clock Divider                                                                        ; 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;

versus

;         -- Reference Clock Frequency                                                                      ; 100.0 MHz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ;
;         -- Output Clock Frequency                                                                         ; 2500.0 MHz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  ;
;         -- L Counter PD Clock Disable                                                                     ; Off                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         ;
;         -- M Counter                                                                                      ; 25                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          ;
;         -- PCIE Frequency Control                                                                         ; pcie_100mhz                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ;
;         -- PD L Counter                                                                                   ; 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;
;         -- PFD L Counter                                                                                  ; 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;
;         -- Powerdown                                                                                      ; Off                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         ;
;         -- Reference Clock Divider                                                                        ; 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ;

In both transceivers, a 2500 MHz clock is generated from a 100 MHz reference clock. It seems like the trick to understanding what’s going on is noting footnote (2) of Table 1-17, saying that the output of L_PD is the one that applies when the PLL is configured as a CDR.

In the first case, the reference clock is fed into the phase detector without division. Since the reference clock is not divided, 100 MHz reaches one input of the phase detector. As the output is divided by PFD_L = 2 and then by M=25, the VCO has to run at 5000 MHz so that its output divided by 50 matches the 100 MHz reference. That doesn’t seem very clever to me (why not pick L=1, and avoid 5 GHz, which I’m not even sure is possible on that silicon?). But at least the math adds up: The output is divided with PD_L = 2, and we have 2500 MHz.

Now to the second case (PCIe): The reference clock is divided by 2, so the phase detector is fed with a 50 MHz reference. The VCO’s clock is divided by PDF_L = 2 and then with M = 25, and hence the VCO runs at 2500 MHz. This way, the total division by 50 (again) matches the 50 MHz reference on the phase detector. PD_L = 1, so the VCO’s output is used undivided, hence an output clock of 2500 MHz, again.

I’m not sure that I’m buying this explanation myself, actually, but it’s the only way I found to make sense of these figures. At some point I tried to convince the tools to divide the reference clock by 2 on the Native PHY (first case above) by adding

set_instance_assignment -name PLL_PFD_CLOCK_FREQUENCY "50 MHz" -to "frontend:frontend_ins|xcvr:xcvr_inst|altera_xcvr_native_av:xcvr_inst|av_xcvr_native:gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|av_pma:inst_av_pma|av_rx_pma:av_rx_pma|rx_pmas[0].rx_pma.rx_cdr"

to the QSF file. This assignment was silently ignored. It wasn’t mentioned anywhere in the reports (not even in the Ignored Assignments) part, but the Divider remained at 1. I should mention that this assignment isn’t documented for Cyclone V, but Quartus Assignment Editor nevertheless agreed to generate it. And Quartus usually refuses to load a project if anything is fishy in the QSF file.

Quartus, timing closure: Obtaining a concise multi-corner timing path report

Introduction

The natural thing to do when an FPGA design fails timing is to take a detailed look at the critical paths, based upon a timing report showing the logic elements and their delays of this path.

If you’re not a heavy user of Intel’s FPGAs (a.k.a. Altera), it may not be so trivial to figure out how to obtain this report. And even worse, you might unknowingly be looking at the wrong report.

The first thing to have sorted out is the concept of multi-corner timing analysis. Without proving that this is sufficient and/or necessary (mainly because I don’t know. Can anyone present a solid proof?), the common practice is to verify an FPGA’s timing validity by ensuring that the timing constraints are met in four cases: The minimal and maximal temperature, and a “slow” and “fast” model, which makes four combinations, or as they are referred to, four corners.

When looking at the critical paths, it’s therefore important to look at the paths at all four corners. This is often overlooked: For example, just generating a timing report in TimeQuest, typically produces the report for a single corner.

So this post describes how to get the report that says something. It relates to Quartus Prime 17.1 Lite.

Everything the said below (including the scripts) works on Quartus Prime 15.1 Lite as well, except that this version (and earlier, I suppose) doesn’t generate any multi-corner reports in any form. This makes the HTML report generation option attractive, as these reports are easier to work with.

I should mention two other related posts in this blog: One taking a look on the relation between input / output constraints and the timing report, and another experimenting a bit with Tcl scripting with TimeQuest.

Getting a multi-corner report: Scripted & quick

First, copy the following Tcl script into a file, say, timing.tcl:

create_timing_netlist
read_sdc
update_timing_netlist

foreach_in_collection op [get_available_operating_conditions] {
  set_operating_conditions $op

  report_timing -setup -npaths 20 -detail full_path -multi_corner \
    -panel_name "Critical paths"
}

Don’t let the “multi_corner” flag confuse you: Each call to report_timing covers one corner. It’s not clear if this flag does anything.

Now to action:

  • In Quartus, expand the TimeQuest group in the Task pane, and open TimeQuest Timing Analyzer.
  • In TimeQuest Timing Analyzer, pick Script > Run Tcl Script… from the menu bar, and select the Tcl script (e.g. timing.tcl).
  • An entry named “Critical paths” is added to the TimeQuest Timing Analyzer’s Report pane. Click on Multi-Corner Summary. A list of paths and their details now fill the main panes.
  • To export all path information into a textual file, right-click Multi-Corner Summary, and select “Export…”. Choose a name for an output file with a .rpt suffix. HTML reports are not supported (they will be empty).

There will also be four separate reports in the same entry, one for each corner. On earlier versions of Quartus, only these will appear (i.e., no Multi-Corner Summary).

Generate HTML / text reports only

The tools can generate neat HTML reports, which are considerably more comfortable to read than TimeQuest’s own GUI. Alas, these reports only cover one corner each. This script generates four HTML reports (it’s a whole bunch of files, JQuery script files, CSS and whatnot. Bells and whistles, but not a multi-corner report).

Suppose the following script as timing-html.tcl

#project_open myproj
create_timing_netlist
read_sdc
update_timing_netlist

foreach_in_collection op [get_available_operating_conditions] {
  set_operating_conditions $op

  report_timing -setup -npaths 20 -detail full_path -multi_corner \
    -file "timing_paths_$op.html" \
    -panel_name "Critical paths for $op"
}

For a plain textual report, change the -file flag’s argument, so the suffix is .rpt or .txt instead of .html.

Note the “project_open” command which is commented out at the top of the script. If it’s uncommented and “myproj” is replaced with the actual project name, a plain shell command line can be used to generate the HTML reports with something like

$ /path/to/quartus/bin/quartus_sta -t timing-html.tcl

I haven’t however found a way to generate a multi-corner report like this.

In order to have these reports generated in each implementation (which is recommended), add a line like the following to the QSF file:

set_global_assignment -name TIMEQUEST_REPORT_SCRIPT relative/path/to/timing-html.tcl

When included in a QSF file, the said Tcl script should not call project_open (comment it out or delete it).

The GUI only method

A multi-corner report can be obtained with just pointing and clicking:

  • In Quartus, expand the TimeQuest group in the Task pane, and open TimeQuest Timing Analyzer.
  • Inside the Timing Analyzer’s Tasks pane, double-click “Update Timing Netlist” .
  • In the same pane, scroll down to “Custom Reports” and double-click “Report Timing…”
  • A dialog box opens. Accept the defaults, and click “Report Timing” below.
  • In the Report pane, an “Report Timing” entry will be added. Expand it and right-click it. In the menu that opens, click “Generate in All Corners”
  • Click on the “Multi Corner Summary” group and possibly export the report as outlined above.

Making any IP in the IP Catalog availabe in QSys

Introduction

I needed the Cyclone V Transceiver Native PHY IP Core inside QSys. Why? Actually, part of a failed attempt to find solve a compilation error.

The IP is available in Quartus 15.1′s IP Catalog, but inside the same toolkit’s QSys it doesn’t appear in the list of IPs. As discussed in this forum thread, this is intentional: Altera doesn’t support having it inside QSys, seemingly because it’s not “fully verified”. OK, so I’ll take the risk. How do I make QSys list this IP, so it can be included?

The fix

As mentioned in this guide, the thing is that IPs which are hidden from QSys have the INTERNAL property set to “true”. All that is left is hence to edit the relevant Tcl file, and update the IP database.

Mission number one is to find the correct Tcl file. The hints on the file’s name are:

  • It’s probably related to the IP’s name and functionality
  • It ends with *_hw.tcl
  • The FPGA family is denoted by “av”, “cv” “sv” etc

Eventually the file I was looking for was at /path/to/quartus/ip/altera/alt_xcvr/altera_xcvr_native_phy/cv/tcl/altera_xcvr_native_cv_hw.tcl. Unlike many other HW Tcl files, it doesn’t just assign parameters directly (in which case it’s easy to spot the assignment to INTERNAL), but it merely consists of adding a couple of directories to some search path, and then it goes:

::altera_xcvr_native_cv::module::declare_module

which refers to module.tcl, which has the following code snippet:

  namespace export \
    declare_module

  # Internal variables
  variable module {\
    {NAME                   VERSION                 INTERNAL  ANALYZE_HDL EDITABLE  ELABORATION_CALLBACK                        PARAMETER_UPGRADE_CALLBACK                    DISPLAY_NAME                        GROUP                                 AUTHOR                DESCRIPTION DATASHEET_URL                                           DESCRIPTION  }\
    {altera_xcvr_native_cv  15.1  true      false       false     ::altera_xcvr_native_cv::module::elaborate  ::altera_xcvr_native_cv::parameters::upgrade  "Cyclone V Transceiver Native PHY"  "Interface Protocols/Transceiver PHY" "Altera Corporation"  NOVAL       "http://www.altera.com/literature/ug/xcvr_user_guide.pdf" "Cyclone V Transceiver Native PHY."}\
  }
}

This is an assignment of multiple variables: The names of the variables are listed on the first curly brackets, and the values in the second. As the third variable is INTERNAL, that’s the one to fix. So the actual edit consist of changing the “true” marked in red above to “false”.

Updating the IP catalog

Only making the change above isn’t enough. The IP Catalog cache must be updated as well.

Change directory to something like /path/to/quartus/ip/altera/ and set up the environment variables:

$ ../../nios2eds/nios2_command_shell.sh

and then create an IP Catalog cache:

$ ip-make-ipx

Once done, overwrite the previous file (you may want to make a copy of it first):

$ mv components.ipx altera_components.ipx

And now restart Quartus. The said IP now appears in QSys’ IP Catalog.

Combining PCIe and Gigabit Transceiver on Cyclone V

Overview

The goal: Using one of the PCIe transceivers for something else on a Cyclone V GT FPGA Development Kit Board, while keeping the PCIe link (narrowing it down from 4x to 2x). This allows allocating some other logic to the transceiver that goes to the PCIe finger, and do the bifurcation with a PCIe extender cable (and some soldering).

In the relevant project, the PCIe block was implemented as a QSys unit, accommodating Altera’s reset and calibration IP blocks.

Use the same controller clock

The first attempt was to just create and instantiate a Cyclone V Transceiver Native PHY IP Core, and place its pins instead of one of the PCIe lanes. But adding a plain MGT transceiver to the design caused the fitter to fail with the following error messages, whether it was supposed to be placed in the same transceiver group of six or not:

Error (14566): The Fitter cannot place 2 periphery component(s) due to conflicts with existing constraints (2 HSSI PMA Aux. block(s)). Fix the errors described in the submessages, and then rerun the Fitter. The Altera Knowledge Database may also contain articles with information on how to resolve this periphery placement failure. Review the errors and then visit the Knowledge Database at https://www.altera.com/support/support-resources/knowledge-base/search.html and search for this specific error message number.
    Error (175001): The Fitter cannot place 1 HSSI PMA Aux. block, which is within Cyclone V Transceiver Native PHY native_xcvr.
        Info (14596): Information about the failing component(s):
            Info (175028): The HSSI PMA Aux. block name(s): native_xcvr:native_xcvr|altera_xcvr_native_av:native_xcvr_inst|av_xcvr_native:gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|av_pma:inst_av_pma|av_tx_pma:av_tx_pma|av_tx_pma_ch:tx_pma_insts[0].av_tx_pma_ch_inst|tx_pma_ch.tx_pma_buf.tx_pma_aux
        Error (178014): Partition assignments may be preventing transceiver placement - transceivers optimizations across partitions are not supported in this version of the Quartus Prime software. For more information, refer to the Release Notes.
    Error (175001): The Fitter cannot place 1 HSSI PMA Aux. block, which is within Cyclone V Transceiver Native PHY native_xcvr.
        Info (14596): Information about the failing component(s):
            Info (175028): The HSSI PMA Aux. block name(s): native_xcvr:native_xcvr|altera_xcvr_native_av:native_xcvr_inst|av_xcvr_native:gen_native_inst.av_xcvr_native_insts[0].gen_bonded_group_native.av_xcvr_native_inst|av_pma:inst_av_pma|av_rx_pma:av_rx_pma|rx_pmas[0].rx_pma.rx_pma_aux
        Error (178014): Partition assignments may be preventing transceiver placement - transceivers optimizations across partitions are not supported in this version of the Quartus Prime software. For more information, refer to the Release Notes.
Error (12289): An error occurred while applying the periphery constraints. Review the offending constraints and rerun the Fitter.

The error messages are horribly misleading, and made me try pushing the transceiver into the QSys project that defines the PCIe interface. Fortunately or unfortunately, I fixed the problem accidentally (or more like out of laziness) and got the idea that it was indeed a matter of structure and partition. See “Leftovers” section below for what I tried and eventually failed.

So the real reason it failed: There are these reconfiguration and reset generating IPs, which are driven by some clock (mgmt_clk on the reconfiguration block), which isn’t necessarily any of the transceiver’s data rate reference clocks. If the PCIe’s reconfiguration IP is driven by a clock that is different from the other transceiver’s, this fitter error occurs.

The solution was to use PCIe’s reference clock for driving the reconfiguration logic of the new transceiver. It could have been the other way around as well, I suppose. Either way, it’s just a utility clock.

Narrow down PCIe to 1x

The next obstacle was that both the PCIe and the general-purpose transceiver relied on two different dedicated reference clock inputs for their PLLs, but with different bit rates. This calls for two different CMUs (PLLs). And according to the Handbook (CV-5V3, figures 2-2 and 2-7), there’s a thing with only the CMUs of CH1 and CH4 being connected directly to these reference clocks. So without diving to deep into the clocking structure, it became apparent that the only way to solve this PLL placement problem was to narrow down the PCIe interface further down to 1x, so it didn’t occupy the CH1 transceiver. I suppose that freed its CMU, and allowed it to generate the clock for some other transceiver.

Also in CV-5V3, figure 4-5 shows that on a PCIe 2x or x4 configuration, CMU PLL is placed in CH4, which is inactive as a transmitter. This goes along with a comment made a page earlier, saying that “The Quartus II software automatically places the CMU PLL in a channel different from that of the data channels”.

And if this doesn’t sound very worked out from my side, it’s because I abandoned this direction and bought an HSMC breakout board instead. So I lost motivation to deal with this issue. Too much hassle.

Leftovers (or: some failed attempts)

Nothing really meaningful here: The jots below were written in my attempts to figure out how and why partitions made any difference (in hindsight: they don’t).

I first tried to put the transceiver as an IP inside the Qsys that holds the PCIe IPs. Which requires some acrobatics, as described in this post. That happened to work, because I used the same clock for both reconfiguration block (and accidentally hit gold without realizing it). Sometimes laziness is a blessing.

Encouraged by the false “I nailed it” feeling, I tried to include the Verilog and SystemVerilog sources of the QSys design, so they’ll be one chunk so to speak, but then the error came back.

One thing that I noted in the map report is that it said:

Info (16010): Generating hard_block partition "hard_block:auto_generated_inst"

So I tried this between synthesis and fitting (merging partitions should do it…?)

$ quartus_cdb theproj --merge=on

Not only did this not make any difference, but the merge report that was generated showed that all HSSI related logic (PMA / PCS elements etc.) was all in the hard_block:auto_generated_inst partition, and none outside it.

Apparently, this hard_block partition is some kind of container for the transceiver-related logic block, and nothing else. Harmless, it seems.

Add to the list of failing attempts: Export the entire design as a post-synthesis QXP, and then run it through quartus_map with only this QXP as a source, followed by the fitter. Which failed the same way. Actually, the same message on generating the hard_block partition appeared in the map report adopting the QXP file.

Also, when going (from shell, then Tcl shell):

$ quartus_sta -s
tcl> project_open theproj
tcl> create_timing_netlist
tcl> report_partitions

The report implied that only the “Top” partition exists (on a design without the added transceiver, or there would be no timing netlist to work with).

Which left me wondering what’s the magic about putting the transceiver inside the QSys design? The answer is of course, none whatsoever. It had nothing to do with partitioning in the first place.

QDB vs. QXP, Quartus Pro vs. Standard: Post-synthesis packaging of an IP core

Introduction

It’s often desired to package an piece of FPGA logic in a post-synthesis (netlist) format for later use in another project. IP core vendors often deliver their products as netlists, partly to protect themselves from unauthorized copying and use, and partly to ensure that possible bugs in the end-user’s synthesizer don’t influence the product they support.

Intel FPGA (formerly Altera) have recently released a new edition of Quartus, codenamed Pro. Intel’s publications indicate that all of their series-10 FPGAs (Cyclone-10, Arria-10, Stratix-10) are covered by the Pro edition only, with the exception of Arria-10 (covered by both editions) and Cyclone-10 LP (covered by the Standard edition).

Among other differences, Quartus Pro doesn’t support the QXP format, which was the netlist-like format used with Quartus Standard. As the Pro edition arrives with a different synthesizer (“quartus_syn” instead of “quartus_map”) and a different internal database structure, the QXP doesn’t fit in.

Aside from this, I can’t comment much on the differences between Standard and Pro, and just by using both, no other difference stands out. As for the new synthesizer: On my own anecdotal experiment with an Arria-10 design that failed timing with the Standard edition, the Pro edition’s ended up with a slightly worse timing result. So on the face of it, there is nothing new and blazing about Pro.

All said in this post relates to Quartus Prime Version 17.1.0 Build 240 SJ Pro Edition running on a Linux machine. I’d expect things to change in future revision (or maybe this is just wishful thinking). The HDL language in this post is Verilog.

The official source of information, followed below, is the Intel Quartus Prime Pro Edition Handbook Volume 1, Design and Compilation, section 7.4, titled “Design Block Reuse”. Intel also offers a free online 45-minute training lecture (Design Block Reuse in the Intel Quartus Prime Pro Software, slides + sound), which pretty much covers the topic.

Update, 8.7.18: Be sure to read the bottom of this post regarding the VQM format.

How to convert a QXP to a QDB

It’s not possible, seemingly because of the difference in the internal database structure.

Packaging a core as a QDB

The desired QDB file is a post-synthesis export of a partition which contains the IP core. Even if the design consists the core to package only, neither an export of the entire synthesized design, nor an export of the root partition will do the trick (or so the Handbook says).

It’s therefore required to generate a wrapper module for the core in question, and instantiate the core in it. This allows defining a dedicated partition at the core’s boundaries. It can be done with the GUI, or a line like the following can be added to the QSF file:

set_instance_assignment -name PARTITION my_core -to my_core_ins -entity top

Where “top” is the name of the toplevel module, which wraps my_core. my_core_ins is the instance name used in the instantiation of my_core.

With this line in place, run Analysis & Synthesis in Quartus, or launch quartus_syn as a command-line utility to synthesize the design.

Once the synthesis is finished, export the core’s Synthesized snapshot with Project > Export Design Partition, and select my_core as the Partition name (it will most likely be the only choice).

Alternatively, use the following command:

quartus_cdb my_core -c my_core --export_partition my_core --snapshot synthesized --file /path/to/my_core.qdb

Note that some information of the file path at which the core was compiled is stored in the qdb file (see e.g. qdb/qar_info.json and sdc.cdb after including the QDB file in the target project) as well as the operating system used (the same file).

Including the QDB file in a design

To include the core in a design, a partition is generated in the target design, and then the QDB file is plugged into that partition by virtue of a QDB_FILE_PARTITION partition. For example:

set_instance_assignment -name PARTITION pr_foo -to foo_ins|my_core_ins
set_instance_assignment -name QDB_FILE_PARTITION /path/to/my_core.qdb -to foo_ins|my_core_ins

In the example above, my_core_ins is the instance name of the core within a module, which is in turn instantiated with the name foo_ins in the toplevel module.

The name pr_foo has no special significance, expect that it’s the name given to the partition.

Unlike the use of QXP files in Quartus’ Standard edition, a black box file must be included in the project: It’s just like the core’s top level module, but with anything between the port declaration and the “endmodule” statement wiped out.

Failing to include a black box file in the project causes a few warnings by fitter like

Warning(13032): The following tri-state nodes are fed by constants
        Warning(13033): The node "foo_ins|my_core_ins" is fed by GND

which escalate to errors, also by the fitter:

Error(13076): The pin "foo_ins|my_core_ins.user_r_read_32_data_w" has multiple drivers due to the non-tri-state driver "foo_ins|my_core_ins"

As a side note, I’d also mention another QSF assignment, QDB_FILE, which may appear to replace QXP_FILE, but attempting to use it with QDB file yields an error:

Error(19507): QDB_FILE assignment "my_core.qdb" is not supported.

Quartus’ help on this error just says that the QDB_FILE assignment isn’t supported, and suggests using QDB_FILE_PARTITION. So it’s not really clear why the QSB_FILE assignment exists.

Matching target device / Quartus revision

Unlike common practice with netlist files, the FPGA part number for which the QDB file was synthesized must match the FPGA part number of the project in which it’s instantiated, or the fitter rejects it with an error like

Error(18097): Partition "|" contains assignment "DEVICE" with setting "10AX115S3F45E2SG", which is different from setting "10AX115S2F45I2VG" in partition "foo_ins|my_core_ins". Modify your design so all partitions use the same setting for the specified assignment.

This finding is contrary to Intel’s training lecture, which says that another FPGA device can be used if the exported snapshot was of post-synthesis type. My experience was different. Also, the Handbook (revision of 2017.11.06) says in Section 7.4.1.4: “Because the exported .qdb includes compiled netlist information, the Consumer project must target the same FPGA device part number and use the same Intel Quartus Prime version as the Developer project.”

I’ve also tried to get an answer on this in Altera’s forum.

So while QXP files can be used freely within an entire device family with Quartus Standard edition, Quartus Pro’s QDB is by far more limited, at least on revision 17.1. And there is no way to generate an EDIF file or anything of that sort in any Quartus edition.

My bet would be that the original intention was to make QDB a true netlist replacement, but then too much of the internal database quirks leaked into the QDB format, making it dependent on both software version and FPGA device. Whether this will be solved, time will tell.

All in all, it’s not clear if a reasonable substitute for a netlist exists at all in Quartus Pro as of writing this. Which is make the choice calling this edition “Pro” quite ironic.

The VQM format

It turns out that Quartus can generate a netlist in VQM format, which is the format used when importing netlists from third-party synthesizers into Quartus (Quartus Pro included).

So after a successful Quartus synthesis, just go

$ quartus_cdb my_core --vqm=my_core.vqm

which generates a post-synthesis netlist in Verilog. It can be imported into any design with the following line in the QSF file (or use the GUI to import it):

set_global_assignment -name VQM_FILE /path/to/my_core.vqm

and it works exactly like a QXP. Or so it did in my own anecdotal experiment: The entire compilation went through cleanly, and the design worked exactly the same on hardware.

The reason I call my own experiment “anecdotal” is the following couple of lines at the very top of the VQM file generated by quartus_cdb:

// !!!!!! This generated VQM is intended for Academic use or Internal Altera use only !!!!!!
// Functionality may not be correct on the programmed device or in simulation

So despite this scary warning, it did work for me. How seriously this warning should be taken is still to find out. Comments are welcome.

Using Verilog “initial” blocks for FPGA synthesis: Legit? Portable?

Introduction

Ask an VLSI engineer if the Verilog “initial” keyword is legit for synthesis, and flames of fury will go your way. However in the FPGA world, it’s a portable way to assign registers and array with the initial (and possibly constant) values immediately after configuration.

The main argument against this practice is that a “real” reset should be used to set the initial values. This is a valid argument, in particular when the clock may be unstable after configuration is done. However it’s often desired to have some kind of reset-generating logic that kicks off immediately after configuration, and this way or another, it depends on the initial wakeup state. Which must be nailed down somehow.

So I made a little survey among the synthesis tools that are relevant for my own work, to verify that it’s safe to use “initial” for this purpose. The answer is yes, in short: The “initial” block is the portable way in Verilog. The references for this conclusion are given below.

Initializing registers

So, is this fine?

reg [7:0] myreg;
initial myreg = 12;
  • Xilinx XST: Yes. Table 7-26 simply says that “All Initial Blocks” are supported in the XST User Guide. An example for initialing an FSM’s state variable is given in “Finite State Machines Coding Examples”. Another example is given in “Specifying INIT Value for a Flip-Flop Coding Examples”. There are also examples of initializing an array by individually setting its entries in this guide.
  • Vivado: Yes, however not said explicitly in the UG901 User Guide. But given the examples showing how to initialize arrays with initial blocks, initializing plain registers go without saying (?).
  • Quartus: Yes. From Chapter 8 of Quartus II Handbook, volume I, Integrated Synthesis: “The software also synthesizes variables
    that are assigned values in Verilog HDL initial blocks into power-up conditions.”

Initializing an array

And what about this?

reg [3:0] mem[0:1023];
initial $readmemh("data.hex", mem);
  • Xilinx XST: Yes. The XST User Guide says in the section named “Verilog System Tasks and Functions Supported in XST”: “The $readmemb and $readmemh system tasks can be used to initialize block memories”. An example for this is given in the section named “Initializing RAM From an External File”.
  • Vivado: Yes. Xilinx’ User Guide UG901 in “Specifying RAM Initial Contents in an External Data File” shown an example.
  • Quartus: Yes. From Chapter 8 of Quartus II Handbook, volume I, Integrated Synthesis: “Quartus II integrated synthesis supports the $readmemb and $readmemh system tasks to initialize memories”

HTG’s USB 3.0 FMC module: Things you surely want to know

Introduction

I purchased a HiTech Global 3-Port USB 3.0 SuperSpeed FMC Module (also known as USB3 FMC), which is an FPGA Mezzanine Card, primarily based upon TI’s SuperSpeed USB 3.0 Transceiver, TUSB1310A. Even though this board works fine at the end of the day, my experience with it was full with surprises, not all of which were helpful. But first:

Don’t connect the USB/FMC board to the FPGA board before reading through this post. There’s a good chance that the FPGA board will feed the board’s 1.8V power supply with 2.5V, which can potentially damage the USB board permanently. More about that below.

The board has three USB 3.0 connectors, two of which are connected to TUSB1310A transceivers, and one going to a GTX transceiver on the FPGA board. The two photos below show the front and back side of the board. I’ve made the red and green dot markings on the USB Micro-B connectors. The red dot marks the useless connector (explained below), the green one is the one that can be used, and goes to the TUSB1310A. The one not marked is connected directly to the GTX (and is useful, albeit with a few issues).

Click to enlarge photos:

HiTech Global HTG-FMC-USB3.0 Rev 1.0, front sideHiTech Global HTG-FMC-USB3.0 Rev 1.0, back side

A few minor irritations

These are a few issues that aren’t all that important each by itself, but they mount up. So let’s have them listed.

  • The schematics isn’t available until you purchase the board.
  • When the package arrived, there was a note telling me to contact support for getting the “product’s documentation for electronic delivery”, as this is an “easy and efficient mechanism for updating the reference designs, user manuals, and other related documents”. What I got was a pdf file with the schematics. That’s it. Meaning, that I had to get the FMC’s pinout from the schematics file. No text-based file (let alone an XDC file for KC705, for example), no user guide, nothing. It’s not just a waste of time to do this manually, but a source for human errors.
  • It says (as of writing this) that a USB3 cable is included, but none was. USB 3.0 Micro-B connectors are different from USB 2.0, so if you’re ordering the board, be sure to acquire a cable as well.
  • Their rate for shipping & handling was $100. This is just annoyingly high, being roughly twice the commonly required amount. And to be fair about it, there was an alternative method for reducing the shipping costs, which required taking care of the logistics myself. Not really important, but nevertheless annoying.

The FMC VADJ supply voltage

The board’s main power supply is VADJ_FMC, which is connected to the 1.8V power net through a 0 Ohm resistor. VADJ_FMC, as its name implies, is an adjustable voltage, generated by the FPGA board. All Xilinx boards I’m aware of have this voltage set to 2.5V by default. So whoever doesn’t pay attention to this issue, connects several components’ VDD-1.8V to 2.5V. Those of the USB transceivers and clock oscillators, that is.

It would, of course, have been much safer to use the standard 3.3V FMC power supply pins (3P3V, the voltage is standardized), and convert it to 1.8V by means of a DC/DC converter on the board, exactly as the board’s 1.1V supply is generated. It wouldn’t solve the voltage incompatibilities of the I/O connections, but there’s a difference between 2.5V on the power supply and 2.5V on the I/O wires.

Either way, there’s no user manual to warn about this, and no warning note in the schematics. Given the risk to literally blow $800, a piece of paper with a warning written in red, when you open the package, is common practice. There was nothing of this sort.

Even though “VADJ” is an “adjustable voltage”, it doesn’t mean it’s easy to adjust it. As this voltage is generated by a sophisticated power supply controller on all Xilinx’ FPGA boards, it requires reprogramming one of these controllers via PMBUS (which is an I2C-like interface). There are, in principle, two ways for doing this:

  • Attaching a USB/PMBUS adapter (available from TI) and programming the power supply with TI’s GUI tool for Windows, Fusion Digital Power. The adapter isn’t expensive ($75 as of writing this), so you probably want to purchase one along with the HTG board.
  • Accessing the power supply controller from the FPGA itself. I’ve written a post on this. Doesn’t require purchasing anything, but it may take quite some effort to set it up. Not recommended.

Regardless of the what way chosen, it involves changing one of the many voltages generated on the FPGA board. Depending on how bad you consider blowing the FPGA board to be, you probably want to spend some time getting acquainted with the power supply controllers, which one controls which voltage etc. No matter how you twist and turn it, you’re one little human error away from feeding the FPGA’s core voltage with 12V.

My post on this will probably help, even though it contains a lot of details that aren’t relevant for the recommended GUI tool + adapter route.

To sum this up: VADJ must be set to 1.8V on the FPGA board before the HTG is attached to the FPGA board with the FMC connector. It takes 5 minutes to do this, once you have the USB adapter at hand, the GUI tool installed and running, and the knowledge of exactly which power rail of which controller, at what PMBUS address it answers to.

One TUSB1310A path is (probably) useless

The board has two ports that are connected to TUSB1310A devices, which are in turn connected to the FMC interface. However if the board is attached to any of Xilinx’ primary development kits, KC705, KCU105 or ML605, only one of these ports can be used. I haven’t checked with other FPGA boards, but I’d expect the situation to be the same. Or maybe the second port can be used for USB 2.0 only. I had no interest in the USB 2.0 part, so I didn’t bother to check.

The problem is that not all FMC pins are connected to the FPGA on these FPGA boards: On KC705 and KCU105, neither boards have the FMC connector’s E21-E37 and F21-F37 pins connected to the FPGA, which are used for the PIPE signals on behalf of the USB port connected to J1 (with prefix P2_* on the relevant signal names in the schematics).

As for ML605, it has almost all FMC connections wired, except for four: FMC pins F37, F38, E36 and E37, which are HB20/HB21_P/N. HB20_P/N are assigned to P2_POWER_DOWN0/1 on the HTG board. These pins are disconnected (floating) on ML605. As they are lacking pulldown resistors, neither physically on the board nor by the chip itself, these wires that control the overall power state of the MGT are left floating. So ML605 can’t be used either.

Maybe there is an FPGA board out there that can use both USB ports. Still to find it.

No GTX reference clock

Even though there is a pair of signals intended as a GTX reference clock, GB_CLK_P/N, this clock pair carries no signal. The reason is that the oscillator that produces this 156.25 MHz reference clock, U9, is an TI LMK61E2 with LVPECL output. Unfortunately, the mandatory termination resistors between the LVPECL output and the capacitors are missing. As a result, the LVPECL outputs (BJT transistor’s emitters, current going only one way)  just charge the capacitors, with no route to discharge them, so there’s no voltage swing, and hence no clock signal.

The obvious workaround is to use another reference clock, hopefully available on the FPGA board (on KC705 I went for the SGMII ref clock at 125 MHz, pins G7/G8).

By the way, the other identical clock oscillator, U5, generates a clock which is available as four differential clocks on the FMC interface, none of which is classified as an MGT clock on the FMC pinout, so odds are that these aren’t wired to the GTX reference pins on any FPGA board. U5 feeds a clock distributor, U9, which is a Microchip SY89833L without any capacitors in the middle. The latter chip has LVPECL-compatible inputs and LVDS outputs, so there is no problem there. It’s just not helpful for the GTX case. For general-purpose use, they are available as CLKo/1_M2C_P/N.

Design errors with the GTX data path

The board’s third USB port, J3, is intended for a direct connection with the FPGA’s GTX via one of the FMC’s dedicated gigabit pins pairs. There are a few issues to be aware of however:

First, the wire pairs are flipped in polarity in both directions (TX and RX), something that is quite apparent when looking on the FMC connector’s wiring. For example, P3_USB30_SSTX_N is connected to DP0_C2M_P. This is quite harmless, since the GTX has RXPOLARITY and TXPOLARITY ports, which can be asserted to to compensate for this error. And the USB 3.0 spec requires that both sides tolerate an P/N flip. And yet, it’s confusing to see the data stream received by the GTX without knowing about this.

Second, capacitors: There are 100 nF capacitors on the receiving wires of J3 (e.g. P3_USB30_SSRX_N) which shouldn’t be there. The standard for USB 3.0, as well as several other protocols, is that the capacitors are on the transmitting side only. Whoever designed the board knew that, because the capacitors of J1 and J2 are placed correctly (on P{1,2}_USB30_SSTX_{N,P} only).

There is a similar issue with the reference clock that is generated for the gigabit transceiver, GB_CLK_P/N: In this case, there are capacitors on the HTG board as well as the FPGA board. This isn’t really a mistake, because there is no standard on which side should put the capacitors, so both sides played safe. And this doesn’t matter, as this reference clock is dead anyhow, as mentioned above.

Putting two 100 nF capacitors in series yields an equivalent capacitor of 50 nF. For the P3_USB30_SSRX_{N,P} wires, this takes capacitance below the minimum allowed per spec, which is 75 nF. This will hardly have any effect on the gigabit data transmission, but may influence the receiver detection mechanism, which measures the current response to a voltage step. Even though a properly designed USB link partner shouldn’t be this fussy.

And one can always fetch the stereoscope and soldering iron, and replace C10 and C11 with 0 Ohm resistors.

By the way, on KC705, KCU105 (and probably all other Xilinx development kits) they’ve placed 100 nF capacitors on the receiving side only of the SMA GTX connectors (the PCIe fingers are done properly, of course). So trying to connect the USB 3.0 wires to the SMA connectors will not work, unless 100 nF capacitors are added in series with the FPGA’s transmission signals. Go figure.

The XCF constraints for KC705

Since I have it, these are the placement constraints for a KC705, as I figured them out from the schematics of the USB board and Xilinx’ reference XCF for the board. I have tested P1 and P3 as USB 3.0, but that doesn’t guarantee all below is correct. The constraints for P2 aren’t given, because they are useless, as explained above. All single-ended pins are LVCMOS18.

set_property PACKAGE_PIN C27 [get_ports CLK0_M2C_N ]
set_property PACKAGE_PIN D27 [get_ports CLK0_M2C_P ]
set_property PACKAGE_PIN D18 [get_ports CLK1_M2C_N ]
set_property PACKAGE_PIN D17 [get_ports CLK1_M2C_P ]
set_property PACKAGE_PIN C8 [get_ports GB_CLK_N ] # No signal
set_property PACKAGE_PIN C7 [get_ports GB_CLK_P ] # No signal
set_property PACKAGE_PIN C25 [get_ports P1_CLKOUT ]
set_property PACKAGE_PIN B24 [get_ports P1_ELAS_BUF_MODE ]
set_property PACKAGE_PIN H21 [get_ports P1_GPIO ]
set_property PACKAGE_PIN C29 [get_ports P1_PHY_RESET_N ]
set_property PACKAGE_PIN B27 [get_ports P1_PHY_STATUS ]
set_property PACKAGE_PIN C30 [get_ports P1_PIPE_RX[0] ]
set_property PACKAGE_PIN D29 [get_ports P1_PIPE_RX[1] ]
set_property PACKAGE_PIN A30 [get_ports P1_PIPE_RX[2] ]
set_property PACKAGE_PIN B30 [get_ports P1_PIPE_RX[3] ]
set_property PACKAGE_PIN D28 [get_ports P1_PIPE_RX[4] ]
set_property PACKAGE_PIN E30 [get_ports P1_PIPE_RX[5] ]
set_property PACKAGE_PIN F30 [get_ports P1_PIPE_RX[6] ]
set_property PACKAGE_PIN H27 [get_ports P1_PIPE_RX[7] ]
set_property PACKAGE_PIN G30 [get_ports P1_PIPE_RX[8] ]
set_property PACKAGE_PIN H24 [get_ports P1_PIPE_RX[9] ]
set_property PACKAGE_PIN H30 [get_ports P1_PIPE_RX[10] ]
set_property PACKAGE_PIN G28 [get_ports P1_PIPE_RX[11] ]
set_property PACKAGE_PIN H26 [get_ports P1_PIPE_RX[12] ]
set_property PACKAGE_PIN E29 [get_ports P1_PIPE_RX[13] ]
set_property PACKAGE_PIN E28 [get_ports P1_PIPE_RX[14] ]
set_property PACKAGE_PIN F28 [get_ports P1_PIPE_RX[15] ]
set_property PACKAGE_PIN D26 [get_ports P1_PIPE_RX_CLK ]
set_property PACKAGE_PIN H25 [get_ports P1_PIPE_RX_K[0] ]
set_property PACKAGE_PIN G29 [get_ports P1_PIPE_RX_K[1] ]
set_property PACKAGE_PIN G27 [get_ports P1_PIPE_RX_VALID ]
set_property PACKAGE_PIN A17 [get_ports P1_PIPE_TX[0] ]
set_property PACKAGE_PIN A18 [get_ports P1_PIPE_TX[1] ]
set_property PACKAGE_PIN A16 [get_ports P1_PIPE_TX[2] ]
set_property PACKAGE_PIN B18 [get_ports P1_PIPE_TX[3] ]
set_property PACKAGE_PIN F17 [get_ports P1_PIPE_TX[4] ]
set_property PACKAGE_PIN A21 [get_ports P1_PIPE_TX[5] ]
set_property PACKAGE_PIN G17 [get_ports P1_PIPE_TX[6] ]
set_property PACKAGE_PIN A20 [get_ports P1_PIPE_TX[7] ]
set_property PACKAGE_PIN C20 [get_ports P1_PIPE_TX[8] ]
set_property PACKAGE_PIN B20 [get_ports P1_PIPE_TX[9] ]
set_property PACKAGE_PIN F18 [get_ports P1_PIPE_TX[10] ]
set_property PACKAGE_PIN A22 [get_ports P1_PIPE_TX[11] ]
set_property PACKAGE_PIN B22 [get_ports P1_PIPE_TX[12] ]
set_property PACKAGE_PIN F21 [get_ports P1_PIPE_TX[13] ]
set_property PACKAGE_PIN G18 [get_ports P1_PIPE_TX[14] ]
set_property PACKAGE_PIN D19 [get_ports P1_PIPE_TX[15] ]
set_property PACKAGE_PIN E19 [get_ports P1_PIPE_TX_CLK ]
set_property PACKAGE_PIN E21 [get_ports P1_PIPE_TX_K[0] ]
set_property PACKAGE_PIN F20 [get_ports P1_PIPE_TX_K[1] ]
set_property PACKAGE_PIN B29 [get_ports P1_POWER_DOWN[0] ]
set_property PACKAGE_PIN A25 [get_ports P1_POWER_DOWN[1] ]
set_property PACKAGE_PIN D21 [get_ports P1_PWRPRESENT ]
set_property PACKAGE_PIN D16 [get_ports P1_RATE ]
set_property PACKAGE_PIN C21 [get_ports P1_RESET_N ]
set_property PACKAGE_PIN F27 [get_ports P1_RX_ELECIDLE ]
set_property PACKAGE_PIN A27 [get_ports P1_RX_POLARITY ]
set_property PACKAGE_PIN B28 [get_ports P1_RX_STATUS[0] ]
set_property PACKAGE_PIN C24 [get_ports P1_RX_STATUS[1] ]
set_property PACKAGE_PIN A28 [get_ports P1_RX_STATUS[2] ]
set_property PACKAGE_PIN A26 [get_ports P1_RX_TERMINATION ]
set_property PACKAGE_PIN G22 [get_ports P1_TX_DEEMPH[0] ]
set_property PACKAGE_PIN C16 [get_ports P1_TX_DEEMPH[1] ]
set_property PACKAGE_PIN B17 [get_ports P1_TX_DETRX_LPBK ]
set_property PACKAGE_PIN C19 [get_ports P1_TX_ELECIDLE ]
set_property PACKAGE_PIN F22 [get_ports P1_TX_MARGIN[0] ]
set_property PACKAGE_PIN D22 [get_ports P1_TX_MARGIN[1] ]
set_property PACKAGE_PIN C22 [get_ports P1_TX_MARGIN[2] ]
set_property PACKAGE_PIN B19 [get_ports P1_TX_ONESZEROS ]
set_property PACKAGE_PIN C17 [get_ports P1_TX_SWING ]
set_property PACKAGE_PIN H14 [get_ports P1_ULPI_CLK ]
set_property PACKAGE_PIN A13 [get_ports P1_ULPI_D[0] ]
set_property PACKAGE_PIN K16 [get_ports P1_ULPI_D[1] ]
set_property PACKAGE_PIN G15 [get_ports P1_ULPI_D[2] ]
set_property PACKAGE_PIN B15 [get_ports P1_ULPI_D[3] ]
set_property PACKAGE_PIN H16 [get_ports P1_ULPI_D[4] ]
set_property PACKAGE_PIN H15 [get_ports P1_ULPI_D[5] ]
set_property PACKAGE_PIN L15 [get_ports P1_ULPI_D[6] ]
set_property PACKAGE_PIN C15 [get_ports P1_ULPI_D[7] ]
set_property PACKAGE_PIN J16 [get_ports P1_ULPI_DIR ]
set_property PACKAGE_PIN L16 [get_ports P1_ULPI_NXT ]
set_property PACKAGE_PIN K15 [get_ports P1_ULPI_STP ]
set_property PACKAGE_PIN E4 [get_ports P3_USB30_SSRX_N ]
set_property PACKAGE_PIN E3 [get_ports P3_USB30_SSRX_P ]
set_property PACKAGE_PIN D2 [get_ports P3_USB30_SSTX_N ]
set_property PACKAGE_PIN D1 [get_ports P3_USB30_SSTX_P ]

Summary

An FMC board for USB 3.0 probably doesn’t sell in large quantities, and it’s quite understandable that its vendor is not interested in spending too much efforts on it. Its design flaws and errors are fairly acceptable once on the table, but given the lack of documentation and supplementary data, the overall picture is not as one would expect from a vendor that has been around for quite a while.

Power supply parameters of Xilinx’ KC705 board, obtained via PMBus

What’s this?

This is a dump of some parameters of the three power supplies on the KC705 board. More precisely, these are the outputs of the “getvals” utility, which is published on this post, running with the FPGA design published on this post.

The control of these power supplies is discussed in another post of mine, “Controlling the power supplies on a Xilinx KC705 FPGA board with PMBus“.

The current readings that rely on a current sensor (some aren’t, and hence read zero) reflect a mostly idling board, with PCIe active.

Hex figures in parentheses are the 16-bit words as obtained from the PMBus slaves, shown alongside with the translation into the relevant voltage, current etc.

U55 (address 52)

Common parameters:
  CAPABILITY = 0xb0
  STATUS_WORD = 0x0003
  STATUS_CML = 0x02
  READ_TEMPERATURE_1 = 30.0625 Celsius (0xdbc2)
  READ_VIN = 12.0469 V (0xd303)
  READ_IIN = 0.0000 A (0x8000)
  VIN_SCALE_MONITOR = 0.1667 V/V (0xa2ab)

Page 0: Controlling DPWM: 1A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.0000 V (0x1000)
  POWER_GOOD_ON = 0.8999 V (0x0e66)
  POWER_GOOD_OFF = 0.8499 V (0x0d99)
  VOUT_MAX = 1.5999 V (0x1999)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 1.1499 V (0x1266)
  VOUT_OV_WARN_LIMIT = 1.0999 V (0x1199)
  VOUT_UV_FAULT_LIMIT = 0.8499 V (0x0d99)
  VOUT_UV_WARN_LIMIT = 0.8999 V (0x0e66)
  IOUT_OC_FAULT_LIMIT = 20.0000 A (0xda80)
  IOUT_OC_WARN_LIMIT = 16.8125 A (0xda1a)
  IOUT_UC_FAULT_LIMIT = 10.7188 A (0xcd5c)
  VOUT_MARGIN_HIGH = 1.0498 V (0x10cc)
  VOUT_MARGIN_LOW = 0.9500 V (0x0f33)
  VOUT_TRANSITION_RATE = 0.2505 V/ms (0xaa01)
  VOUT_SCALE_LOOP = 1.0000 V/V (0xba00)
  VOUT_SCALE_MONITOR = 1.0000 V/V (0xba00)
  READ_VOUT = 1.0042 V (0x1011)
  READ_IOUT = 0.4062 A (0xab40)

Page 1: Controlling DPWM: 2A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.7998 V (0x1ccc)
  POWER_GOOD_ON = 1.6199 V (0x19eb)
  POWER_GOOD_OFF = 1.5298 V (0x187a)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 2.0698 V (0x211e)
  VOUT_OV_WARN_LIMIT = 1.9800 V (0x1fae)
  VOUT_UV_FAULT_LIMIT = 1.5298 V (0x187a)
  VOUT_UV_WARN_LIMIT = 1.6199 V (0x19eb)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 1.8899 V (0x1e3d)
  VOUT_MARGIN_LOW = 1.7100 V (0x1b5c)
  VOUT_TRANSITION_RATE = 0.2510 V/ms (0xaa02)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 1.8040 V (0x1cdd)
  READ_IOUT = 0.1094 A (0x9b80)

Page 2: Controlling DPWM: 3A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 3.2998 V (0x34cc)
  POWER_GOOD_ON = 2.9700 V (0x2f85)
  POWER_GOOD_OFF = 2.8049 V (0x2ce1)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 3.7949 V (0x3cb8)
  VOUT_OV_WARN_LIMIT = 3.6299 V (0x3a14)
  VOUT_UV_FAULT_LIMIT = 2.8049 V (0x2ce1)
  VOUT_UV_WARN_LIMIT = 2.9700 V (0x2f85)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 3.4648 V (0x3770)
  VOUT_MARGIN_LOW = 3.1348 V (0x3228)
  VOUT_TRANSITION_RATE = 0.2400 V/ms (0xa3d7)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 3.2749 V (0x3466)
  READ_IOUT = 0.4531 A (0xaba0)

Page 3: Controlling DPWM: 4A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 2.5000 V (0x2800)
  POWER_GOOD_ON = 1.7000 V (0x1b33)
  POWER_GOOD_OFF = 1.6499 V (0x1a66)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 2.8750 V (0x2e00)
  VOUT_OV_WARN_LIMIT = 2.7500 V (0x2c00)
  VOUT_UV_FAULT_LIMIT = 2.1250 V (0x2200)
  VOUT_UV_WARN_LIMIT = 2.2500 V (0x2400)
  IOUT_OC_FAULT_LIMIT = 10.4062 A (0xd29a)
  IOUT_OC_WARN_LIMIT = 8.4062 A (0xd21a)
  IOUT_UC_FAULT_LIMIT = 5.3594 A (0xc55c)
  VOUT_MARGIN_HIGH = 2.6250 V (0x2a00)
  VOUT_MARGIN_LOW = 2.3750 V (0x2600)
  VOUT_TRANSITION_RATE = 0.2510 V/ms (0xaa02)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 2.4958 V (0x27ef)
  READ_IOUT = 0.0000 A (0x8000)

U56 (address 53)

Common parameters:
  CAPABILITY = 0xb0
  STATUS_WORD = 0x0003
  STATUS_CML = 0x02
  READ_TEMPERATURE_1 = 33.3750 Celsius (0xe216)
  READ_VIN = 12.0156 V (0xd301)
  READ_IIN = 0.0000 A (0x8000)
  VIN_SCALE_MONITOR = 0.1667 V/V (0xa2ab)

Page 0: Controlling DPWM: 1A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 2.5000 V (0x2800)
  POWER_GOOD_ON = 2.2500 V (0x2400)
  POWER_GOOD_OFF = 2.1250 V (0x2200)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 2.8750 V (0x2e00)
  VOUT_OV_WARN_LIMIT = 2.7500 V (0x2c00)
  VOUT_UV_FAULT_LIMIT = 2.1250 V (0x2200)
  VOUT_UV_WARN_LIMIT = 2.2500 V (0x2400)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 2.6250 V (0x2a00)
  VOUT_MARGIN_LOW = 2.3750 V (0x2600)
  VOUT_TRANSITION_RATE = 0.2510 V/ms (0xaa02)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 2.5110 V (0x282d)
  READ_IOUT = 0.0000 A (0x8000)

Page 1: Controlling DPWM: 2A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.5000 V (0x1800)
  POWER_GOOD_ON = 1.3499 V (0x1599)
  POWER_GOOD_OFF = 1.2749 V (0x1466)
  VOUT_MAX = 1.5999 V (0x1999)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 1.7249 V (0x1b99)
  VOUT_OV_WARN_LIMIT = 1.6499 V (0x1a66)
  VOUT_UV_FAULT_LIMIT = 1.2749 V (0x1466)
  VOUT_UV_WARN_LIMIT = 1.3499 V (0x1599)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 1.5750 V (0x1933)
  VOUT_MARGIN_LOW = 1.4248 V (0x16cc)
  VOUT_TRANSITION_RATE = 0.2515 V/ms (0xaa03)
  VOUT_SCALE_LOOP = 1.0000 V/V (0xba00)
  VOUT_SCALE_MONITOR = 1.0000 V/V (0xba00)
  READ_VOUT = 1.5024 V (0x180a)
  READ_IOUT = 0.0000 A (0x8000)

Page 2: Controlling DPWM: 3A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.0000 V (0x1000)
  POWER_GOOD_ON = 0.8999 V (0x0e66)
  POWER_GOOD_OFF = 0.8499 V (0x0d99)
  VOUT_MAX = 1.5999 V (0x1999)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 1.4500 V (0x1733)
  VOUT_OV_WARN_LIMIT = 1.4199 V (0x16b8)
  VOUT_UV_FAULT_LIMIT = 0.8499 V (0x0d99)
  VOUT_UV_WARN_LIMIT = 0.8999 V (0x0e66)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 1.3999 V (0x1666)
  VOUT_MARGIN_LOW = 0.9500 V (0x0f33)
  VOUT_TRANSITION_RATE = 0.2505 V/ms (0xaa01)
  VOUT_SCALE_LOOP = 1.0000 V/V (0xba00)
  VOUT_SCALE_MONITOR = 1.0000 V/V (0xba00)
  READ_VOUT = 0.9993 V (0x0ffd)
  READ_IOUT = 0.9844 A (0xb3f0)

Page 3: Controlling DPWM: 4A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.2000 V (0x1333)
  POWER_GOOD_ON = 1.0798 V (0x1147)
  POWER_GOOD_OFF = 1.0198 V (0x1051)
  VOUT_MAX = 1.5999 V (0x1999)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 1.3799 V (0x1614)
  VOUT_OV_WARN_LIMIT = 1.3198 V (0x151e)
  VOUT_UV_FAULT_LIMIT = 1.0198 V (0x1051)
  VOUT_UV_WARN_LIMIT = 1.0798 V (0x1147)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 1.2598 V (0x1428)
  VOUT_MARGIN_LOW = 1.1399 V (0x123d)
  VOUT_TRANSITION_RATE = 0.2515 V/ms (0xaa03)
  VOUT_SCALE_LOOP = 1.0000 V/V (0xba00)
  VOUT_SCALE_MONITOR = 1.0000 V/V (0xba00)
  READ_VOUT = 1.1973 V (0x1328)
  READ_IOUT = 1.0625 A (0xba20)

U89 (address 54)

Common parameters:
  CAPABILITY = 0xb0
  STATUS_WORD = 0x0003
  STATUS_CML = 0x02
  READ_TEMPERATURE_1 = 35.0625 Celsius (0xe231)
  READ_VIN = 12.0781 V (0xd305)
  READ_IIN = 0.0000 A (0x8000)
  VIN_SCALE_MONITOR = 0.1667 V/V (0xa2ab)

Page 0: Controlling DPWM: 1A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 2.0000 V (0x2000)
  POWER_GOOD_ON = 1.7998 V (0x1ccc)
  POWER_GOOD_OFF = 1.7000 V (0x1b33)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 2.2000 V (0x2333)
  VOUT_OV_WARN_LIMIT = 2.2000 V (0x2333)
  VOUT_UV_FAULT_LIMIT = 1.7000 V (0x1b33)
  VOUT_UV_WARN_LIMIT = 1.7998 V (0x1ccc)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 2.0999 V (0x2199)
  VOUT_MARGIN_LOW = 1.8999 V (0x1e66)
  VOUT_TRANSITION_RATE = 0.2510 V/ms (0xaa02)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 1.9985 V (0x1ffa)
  READ_IOUT = 0.0000 A (0x8000)

Page 1: Controlling DPWM: 2A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.0000 V (0x1000)
  POWER_GOOD_ON = 0.8999 V (0x0e66)
  POWER_GOOD_OFF = 0.8499 V (0x0d99)
  VOUT_MAX = 1.5999 V (0x1999)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 1.1499 V (0x1266)
  VOUT_OV_WARN_LIMIT = 1.0999 V (0x1199)
  VOUT_UV_FAULT_LIMIT = 0.8499 V (0x0d99)
  VOUT_UV_WARN_LIMIT = 0.8999 V (0x0e66)
  IOUT_OC_FAULT_LIMIT = 13.0156 A (0xd341)
  IOUT_OC_WARN_LIMIT = 10.5156 A (0xd2a1)
  IOUT_UC_FAULT_LIMIT = 4.7031 A (0xc4b4)
  VOUT_MARGIN_HIGH = 1.0498 V (0x10cc)
  VOUT_MARGIN_LOW = 0.9500 V (0x0f33)
  VOUT_TRANSITION_RATE = 0.2505 V/ms (0xaa01)
  VOUT_SCALE_LOOP = 1.0000 V/V (0xba00)
  VOUT_SCALE_MONITOR = 1.0000 V/V (0xba00)
  READ_VOUT = 1.0005 V (0x1002)
  READ_IOUT = 0.0000 A (0x8000)

Page 2: Controlling DPWM: 3A
  OPERATION = 0x40
  ON_OFF_CONFIG = 0x00
  VOUT_MODE = 0x14
  STATUS_VOUT = 0x00
  STATUS_IOUT = 0x00
  STATUS_INPUT = 0x00
  STATUS_TEMPERATURE = 0x00
  VOUT_COMMAND = 1.7998 V (0x1ccc)
  POWER_GOOD_ON = 1.6199 V (0x19eb)
  POWER_GOOD_OFF = 1.5298 V (0x187a)
  VOUT_MAX = 3.6328 V (0x3a20)
  VOUT_CAL_OFFSET = 0.0000 V, signed (0x0000)
  VOUT_OV_FAULT_LIMIT = 2.0698 V (0x211e)
  VOUT_OV_WARN_LIMIT = 1.9800 V (0x1fae)
  VOUT_UV_FAULT_LIMIT = 1.5298 V (0x187a)
  VOUT_UV_WARN_LIMIT = 1.6199 V (0x19eb)
  IOUT_OC_FAULT_LIMIT = 10.4062 A (0xd29a)
  IOUT_OC_WARN_LIMIT = 8.4062 A (0xd21a)
  IOUT_UC_FAULT_LIMIT = 5.3594 A (0xc55c)
  VOUT_MARGIN_HIGH = 1.8899 V (0x1e3d)
  VOUT_MARGIN_LOW = 1.7100 V (0x1b5c)
  VOUT_TRANSITION_RATE = 0.2510 V/ms (0xaa02)
  VOUT_SCALE_LOOP = 0.4404 V/V (0xab86)
  VOUT_SCALE_MONITOR = 0.4404 V/V (0xab86)
  READ_VOUT = 1.8108 V (0x1cf9)
  READ_IOUT = 0.0000 A (0x8000)

Example C sources for controlling a power supply with PMBus

Overview

This post is best read after another post of mine, “Controlling the power supplies on a Xilinx KC705 FPGA board with PMBus“. This C code assumes that the FPGA is loaded with the design published in this post.

These are the sources of utilities I wrote to fetch parameters and update the VADJ voltage on a Xilinx KC705 FPGA board. It may be useful as a base for other UDC92xx devices or PMBus-controlled devices in general. Just be sure you know what you’re doing. Which brings me to…

Warning: Issuing the wrong command to a power supply controller can destroy an FPGA board in a split second. I take no responsibility for any damage, even if something I’ve written is misleading or outright wrong. It’s YOUR full responsibility to double-check any of your actions.

I ran this on a Linux PC host, hence little Endian, to which the KC705 was attached with Xillybus over PCIe. The Xillybus over PCIe part allowed controlling the PMBus with a little piece of logic (given on this post). It’s worth to mention that Xillybus is a major overkill for this tiny data exchange, but given that the PCIe link is already there, it’s a convenient choice.

The pmbus_read() and pmbus_write() in pmbus.c below should be adapted if any other solution for accessing the hardware is chosen. I suppose Linux’ native I2C driver would work, even though I haven’t tried it.

The interface with the PMBus logic in the FPGA through /dev/xillybus_pmbus is in principle as follows:

  • Opening /dev/xillybus_pmbus for write issues a START condition. Closing it while no file handle has /dev/xillybus_pmbus opened for read issues a STOP. But if /dev/xillybus_pmbus is opened for read by some file handle while the write-open filehandle closes, and then this device file is opened for write again, the second open() causes a RESTART condition.
  • Each byte written to the write filehandle causes a byte written on the PMBus. If the first byte of a file indicated a read bus address, all following bytes are turned into 0xff  on the physical wires by the logic (i.e. high-Z), regardless of the sent data, so that the bus slave’s response can be sensed.
  • For each byte that was forced into 0xff due to a read bus address, a byte is available for reception on the read filehandle, if such is open.

The PMBus logic on the FPGA side is something I’ve hacked along for different needs, so it doesn’t cover corner cases well, however it works well for its purpose. See the notes in the this post.

Now to the utilities.

pmbus.c: The common library function

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

#include "pmbus.h"

// Note that crc8 can be pre-calculated into an array of 256 bytes
static unsigned char crc8(unsigned char x) {
  int i;

  for (i=0; i<8; i++) {
    char toxor = (x & 0x80) ? 0x07 : 0;

    x = x << 1;
    x = x ^ toxor;
  }

  return x;
}

static unsigned char smbus_pec(unsigned char crc, const unsigned char *p,
			       int count) {
  int i;

  for (i = 0; i < count; i++)
    crc = crc8(crc ^ p[i]);
  return crc;
}

void pmbus_write(int addr, int command, int length, unsigned char *data) {
  int fd, rc;

  unsigned char sendbuf[131];
  int sent, tosend;

  if (length > 128)
    exit(1);

  fd = open("/dev/xillybus_pmbus", O_WRONLY);
  if (fd < 0) {
    perror("Failed to open /dev/xillybus_pmbus write-only");
    exit(1);
  }

  sendbuf[0] = addr & 0xfe;
  sendbuf[1] = command;
  memcpy(sendbuf + 2, data, length);
  sendbuf[length + 2] = smbus_pec(0, sendbuf, length+2);

  for (sent = 0, tosend = length + 3; sent < tosend; sent += rc) {
    rc = write(fd, (void *) (sendbuf + sent), tosend - sent);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0)
      perror("pmbus_write() failed to write");

    if (rc == 0)
      fprintf(stderr, "pmbus_write reached write EOF (?!)\n");

    if (rc <= 0) {
      close(fd);
      exit(1);
    }
  }

  close(fd);
}

void pmbus_read(int addr, int command, int length, unsigned char *data) {
  int fdr, fdw, rc, i;

  unsigned char cmdbuf[2] = { addr & 0xfe, command };
  unsigned char dummybuf[130];
  unsigned char crc;

  const unsigned char *sendbuf[2] = { cmdbuf, dummybuf };
  const int tosend[2] = { 2, length + 2 };
  int sent, recvd;

  if (length > 128)
    exit(1);

  memset(dummybuf, 0, sizeof(dummybuf));
  dummybuf[0] = addr | 1;

  fdr = open("/dev/xillybus_pmbus", O_RDONLY);

  if (fdr < 0) {
    perror("Failed to open /dev/xillybus_pmbus read-only");
    exit(1);
  }

  for (i=0; i<2; i++) {
    fdw = open("/dev/xillybus_pmbus", O_WRONLY);
    if (fdw < 0) {
      perror("Failed to open /dev/xillybus_pmbus write-only");
      exit(1);
    }

    for (sent = 0; sent < tosend[i]; sent += rc) {
      rc = write(fdw, (void *) (sendbuf[i] + sent), tosend[i] - sent);

      if ((rc < 0) && (errno == EINTR))
	continue;

      if (rc < 0)
	perror("pmbus_read() failed to write");

      if (rc == 0)
	fprintf(stderr, "pmbus_read reached write EOF (?!)\n");

      if (rc <= 0) {
	close(fdr);
	close(fdw);
	exit(1);
      }
    }

    // Force a restart on the PMBUS by closing fdw. Had fdr been closed as
    // well, we would get a stop condition instead.

    close(fdw);
  }

  crc = smbus_pec(0, sendbuf[0], tosend[0]);
  crc = smbus_pec(crc, sendbuf[1], 1);

  // Collect the data that was read during the dummy write data cycles
  for (recvd = 0; recvd < length + 1; recvd += rc) {
    rc = read(fdr, (void *) (dummybuf + recvd), length + 1 - recvd);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0)
      perror("pmbus_read() failed to read");

    if (rc == 0)
      fprintf(stderr, "pmbus_read reached EOF (?!)\n");

    if (rc <= 0) {
      close(fdr);
      exit(1);
    }
  }

  close(fdr);

  memcpy(data, dummybuf, length);

  crc = smbus_pec(crc, dummybuf, length);

  if (crc != dummybuf[length]) {
    fprintf(stderr, "Failed CRC check: PEC = 0x%02x, received 0x%02x\n",
	    crc, dummybuf[length]);
    exit(1);
  }
}

void set_page(int addr, unsigned char page) {
  unsigned char read_page;

  pmbus_write(addr, 0x00, 1, &page);

  pmbus_read(addr, 0x00, 1, &read_page);

  if (page != read_page) {
    fprintf(stderr, "Failed to set page to %d (read back %d instead)\n",
	    page, read_page);
    exit(1);
  }
}

void get_phase_info(int addr, unsigned char *data) {
  unsigned char readbytes[5];

  pmbus_read(addr, 0xd2, 5, readbytes);

  if (readbytes[0] != 4) {
    fprintf(stderr, "PHASE_INFO responded with block length %d != 4\n",
	    readbytes[0]);
    exit(1);
  }

  memcpy(data, readbytes + 1, 4);
}

void print_dpwms(unsigned char phase_info) {
  const char *dpwm[8] = { "1A", "1B", "2A", "2B", "3A", "3B", "4A", "4B" };
  int i;

  for (i=0; i<8; i++) {
    if (phase_info & 1)
      printf(" %s", dpwm[i]);
    phase_info >>= 1;
  }
}

void clear_faults(int addr) {
  pmbus_write(addr, 0x03, 0, NULL);
}

double linear2voltage(unsigned short word) {
  return word / 4096.0;
}

double linear2nonvoltage(unsigned short word) {
  double x = word & 0x7ff;
  int exponent = (word >> 11) & 0xf;

  if (word & 0x8000)
    x /= 1 << (16 - exponent);
  else
    x *= 1 << exponent;

  return x;
}

pmbus.h: The header file

void pmbus_write(int addr, int command, int length, unsigned char *data);
void pmbus_read(int addr, int command, int length, unsigned char *data);
void set_page(int addr, unsigned char page);
void get_phase_info(int addr, unsigned char *data);
void print_dpwms(unsigned char phase_info);
void clear_faults(int addr);

double linear2voltage(unsigned short word);
double linear2nonvoltage(unsigned short word);

getvals.c: Dump selected parameters

This command-line utility fetches and displays some selected parameters from the power supply controller.

Note that the PMBus address is hardcoded as 52 in main(). It’s multiplied by two for an 8-bit representation.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "pmbus.h"

enum { PM_VOLTAGE, PM_NONVOLTAGE, PM_STATUS } pm_types ;

struct pm_list {
  unsigned char command;
  int size;
  char *name;
  char *units;
  int type;
};

static struct pm_list pm_common[] = {
  { 0x19, 1, "CAPABILITY", "", PM_STATUS },
  { 0x79, 2, "STATUS_WORD", "", PM_STATUS },
  { 0x7e, 1, "STATUS_CML", "", PM_STATUS },
  { 0x8d, 2, "READ_TEMPERATURE_1", "Celsius", PM_NONVOLTAGE },
  { 0x88, 2, "READ_VIN", "V", PM_NONVOLTAGE },
  { 0x89, 2, "READ_IIN", "A", PM_NONVOLTAGE },
  { 0xd3, 2, "VIN_SCALE_MONITOR", "V/V", PM_NONVOLTAGE },
  { }
};

static struct pm_list pm_paged[] = {
  { 0x01, 1, "OPERATION", "", PM_STATUS },
  { 0x02, 1, "ON_OFF_CONFIG", "", PM_STATUS },
  { 0x20, 1, "VOUT_MODE", "", PM_STATUS },
  { 0x7a, 1, "STATUS_VOUT", "", PM_STATUS },
  { 0x7b, 1, "STATUS_IOUT", "", PM_STATUS },
  { 0x7c, 1, "STATUS_INPUT", "", PM_STATUS },
  { 0x7d, 1, "STATUS_TEMPERATURE", "", PM_STATUS },
  { 0x21, 2, "VOUT_COMMAND", "V", PM_VOLTAGE },
  { 0x5e, 2, "POWER_GOOD_ON", "V", PM_VOLTAGE },
  { 0x5f, 2, "POWER_GOOD_OFF", "V", PM_VOLTAGE },
  { 0x24, 2, "VOUT_MAX", "V", PM_VOLTAGE },
  { 0x23, 2, "VOUT_CAL_OFFSET", "V, signed", PM_VOLTAGE },
  { 0x40, 2, "VOUT_OV_FAULT_LIMIT", "V", PM_VOLTAGE },
  { 0x42, 2, "VOUT_OV_WARN_LIMIT", "V", PM_VOLTAGE },
  { 0x44, 2, "VOUT_UV_FAULT_LIMIT", "V", PM_VOLTAGE },
  { 0x43, 2, "VOUT_UV_WARN_LIMIT", "V", PM_VOLTAGE },
  { 0x46, 2, "IOUT_OC_FAULT_LIMIT", "A", PM_NONVOLTAGE },
  { 0x4a, 2, "IOUT_OC_WARN_LIMIT", "A", PM_NONVOLTAGE },
  { 0x4b, 2, "IOUT_UC_FAULT_LIMIT", "A", PM_NONVOLTAGE },  

  { 0x25, 2, "VOUT_MARGIN_HIGH", "V", PM_VOLTAGE },
  { 0x26, 2, "VOUT_MARGIN_LOW", "V", PM_VOLTAGE },
  { 0x27, 2, "VOUT_TRANSITION_RATE", "V/ms", PM_NONVOLTAGE },
  { 0x29, 2, "VOUT_SCALE_LOOP", "V/V", PM_NONVOLTAGE },
  { 0x2a, 2, "VOUT_SCALE_MONITOR", "V/V", PM_NONVOLTAGE },
  { 0x8b, 2, "READ_VOUT", "V", PM_VOLTAGE },
  { 0x8c, 2, "READ_IOUT", "A", PM_NONVOLTAGE },
 { }
};

static void dumpval(int pmbus_addr, struct pm_list *p) {
  unsigned short val;

  pmbus_read(pmbus_addr, p->command, p->size, (void *) &val);

  switch (p->type) {
  case PM_VOLTAGE:
    printf("  %s = %.4f %s (0x%04x)\n", p->name, linear2voltage(val),
	   p->units, val);
    break;

  case PM_NONVOLTAGE:
    printf("  %s = %.4f %s (0x%04x)\n", p->name, linear2nonvoltage(val),
	   p->units, val);
    break;

  default:
    if (p->size == 1)
      printf("  %s = 0x%02x\n", p->name, val & 0xff);
    else
      printf("  %s = 0x%04x\n", p->name, val);
    break;
  }
}

int main(int argc, char *argv[]) {
  int i;
  struct pm_list *p;

  unsigned char phase_info[4];

  int pmbus_addr = 52 * 2;

  printf("Common parameters:\n");

  for (p = pm_common; p->name; p++)
    dumpval(pmbus_addr, p);

  get_phase_info(pmbus_addr, phase_info);

  for (i=0; i<4; i++) {
    if (phase_info[i] == 0)
      continue;

    printf("\nPage %d: Controlling DPWM:", i);
    print_dpwms(phase_info[i]);
    printf("\n");

    set_page(pmbus_addr, i);

    for (p = pm_paged; p->name; p++)
      dumpval(pmbus_addr, p);
  }

  return 0;
}

updatevoltage.c: Change the voltage of a rail

This is the scary part. It’s written so the voltage can be updated to a lower one without turning off the power rail. Note that the new voltage is hardcoded as new_voltage (1.8V). In main(), the PMBus address is hardcoded as 52, and the page to access as 3. Before changing the page to be accessed, be sure to understand how they relate to power rail. Anyhow, the utility says which power rail it’s going to play with, so pay attention to that.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "pmbus.h"
#include <string.h>

static double new_voltage = 1.8;

enum { PM_VOLTAGE, PM_NONVOLTAGE, PM_STATUS } pm_types ;

struct pm_list {
  unsigned char command;
  int size;
  char *name;
  double diversion;
  char *units;
  int type;
};

static struct pm_list pm_paged[] = {
  { 0x25, 2, "VOUT_MARGIN_HIGH", 5.0, "V", PM_VOLTAGE },
  { 0x26, 2, "VOUT_MARGIN_LOW", -5.0, "V", PM_VOLTAGE },
  { 0x5e, 2, "POWER_GOOD_ON", -5.0, "V", PM_VOLTAGE },
  { 0x5f, 2, "POWER_GOOD_OFF", -8.0, "V", PM_VOLTAGE },
  { 0x43, 2, "VOUT_UV_WARN_LIMIT", -10.0, "V", PM_VOLTAGE },
  { 0x44, 2, "VOUT_UV_FAULT_LIMIT", -15.0, "V", PM_VOLTAGE },
  { 0x21, 2, "VOUT_COMMAND", 0.0, "V", PM_VOLTAGE },
  { 0x40, 2, "VOUT_OV_FAULT_LIMIT", 15.0, "V", PM_VOLTAGE },
  { 0x42, 2, "VOUT_OV_WARN_LIMIT", 10.0, "V", PM_VOLTAGE },
 { }
};

static unsigned short new_val(struct pm_list *p) {
  return (unsigned short) ((4096.0 * new_voltage * (1.0 + p->diversion / 100.0)) + 0.5);
}

static void dumpval(int pmbus_addr, struct pm_list *p) {
  unsigned short val;

  pmbus_read(pmbus_addr, p->command, p->size, (void *) &val);

  switch (p->type) {
  case PM_VOLTAGE:
    printf("  %s = %.4f %s -> %.4f %s (0x%04x -> 0x%04x)\n",
	   p->name, linear2voltage(val),
	   p->units, linear2voltage(new_val(p)), p->units,
	   val, new_val(p));
    break;

  case PM_NONVOLTAGE:
    printf("  %s = %.4f %s\n", p->name, linear2nonvoltage(val),
	   p->units);
    break;

  default:
    if (p->size == 1)
      printf("  %s = 0x%02x\n", p->name, val & 0xff);
    else
      printf("  %s = 0x%04x\n", p->name, val);
    break;
  }
}

int main(int argc, char *argv[]) {
  int dry_run = 0;
  char *wrote = "WRITING";

  struct pm_list *p;

  unsigned char phase_info[4];

  int pmbus_addr = 52 * 2;
  int page = 3;
  char yes[10];

  get_phase_info(pmbus_addr, phase_info);

  if (phase_info[page] == 0) {
    fprintf(stderr, "Requested page %d is not active on device\n", page);
    return 1;
  }
  printf("\nPage %d: Controlling DPWM:", page);
  print_dpwms(phase_info[page]);
  printf("\n");

  set_page(pmbus_addr, page);

  for (p = pm_paged; p->name; p++)
    dumpval(pmbus_addr, p);

  printf("\nUpdate these new values? (Type uppercase YES): ");
  fgets(yes, sizeof(yes), stdin);

  if (strcmp(yes, "YES\n")) {
    printf("\nDidn't get YES. Therefore dry-running.\n");
    dry_run = 1;
    wrote = "Would write";
  }

  for (p = pm_paged; p->name; p++) {
    unsigned short v = new_val(p);
    printf("%s value 0x%04x (%.4f V) to %s (command 0x%02x)\n",
	   wrote, v, linear2voltage(v), p->name, p->command);

    if (!dry_run) {
      unsigned short r;
      pmbus_write(pmbus_addr, p->command, 2, (void *) &v);
      pmbus_read(pmbus_addr, p->command, 2, (void *) &r);

      if (v != r) {
	fprintf(stderr, "Readback failed! Wrote 0x%04x, read back 0x%04x\n",
		v, r);
	exit(1);
      }
    }
  }

  if (dry_run)
    return 0;

  printf("\nCheck the voltage now.\n\nStore current settings to non-volatile memory? (Type uppercase STORE): ");
  fgets(yes, sizeof(yes), stdin);

  if (strcmp(yes, "STORE\n")) {
    printf("OK, did nothing.\n");
  } else {
    printf("Setting page back to 0\n");
    set_page(pmbus_addr, 0);

    printf("SENDING a STORE_DEFAULT_ALL command\n");
    pmbus_write(pmbus_addr, 0x11, 0, NULL);
    printf("Done.\n");
  }

  return 0;
}

The Makefile

If we’re at it:

CC=	gcc
ALL=	getvals updatevoltage
OBJECTS = pmbus.o
HEADERFILES = pmbus.h
LIBFLAGS=
FLAGS=	-Wall -O3 -g

all:	$(ALL)

clean:
	rm -f *.o $(ALL)
	rm -f `find . -name "*~"`

%.o:	%.c $(HEADERFILES)
	$(CC) -c $(FLAGS) -o $@ $<

$(ALL) : %: %.o Makefile $(OBJECTS)
	$(CC) $< $(OBJECTS) -o $@ $(LIBFLAGS)

gcc and GNU Make need to be installed, of course.