Reverse engineering Cyclone 10 transceiver’s attributes

This post was written by eli on October 14, 2021
Posted Under: FPGA,GTX,Intel FPGA (Altera)

Introduction

This post summarizes some scattered findings I made while trying to make a Cyclone 10′s signal detect feature work properly for detecting a SuperSpeed USB LFPS signal. As it turned out, Cyclone 10′s transceiver isn’t capable of this, as explained below.

But since the documentation on this issue was lacking, I resorted to reverse engineering Quartus in the attempt to find a solution. So this post is a bit about the transceiver and more about the reverse engineering efforts, which might be relevant in completely different contexts.

I should mention that everything on this page relates to Cyclone 10, even though the output from the tools keep naming different logic elements with “a10″, as if it was Arria 10. Clearly, the transceivers for the two FPGA families are the same.

Software: Quartus Pro 17.1 running on a 64-bit Linux machine (Mint 19).

Cyclone 10′s signal detect is rubbish

The purpose of the signal detector is to tell whether the differential wires are in an electrical idle state, or if there’s some activity on these. This is used by several protocols to wake up the link partners from a low power state: A PCIe link can be awaken by an upstream facing link partner (typically the device waking up the host) from a L2 state by virtue of a beacon, which consists of toggling the polarity at a rate of 30 kHz — 500 MHz. A SATA link can be awaken by one of the link partners transmitting a special data pattern. The USB 3.x protocol also uses out-of-band (OOB) signals if this sort, for various purposes, and calls them LFPS (Low Frequency Pulse Signaling). The toggling rate is defined between 10 — 50 MHz.

The first, relatively simple obstacle, was to turn on the signal detector. The Cyclone 10 GX Transceiver PHY User Guide says in Table 67, regarding rx_std_signaldetect:

When enabled, the signal threshold detection circuitry senses whether the signal level present at the RX input buffer is above the signal detect threshold voltage. You can specify the signal detect threshold using a Quartus Prime Settings File (.qsf) assignment. This signal is required for the PCI Express, SATA and SAS protocols.

Similar notes are made in other places in that guide. However it doesn’t mention that if the transceiver is configured in “basic” mode (as opposed to SATA mode, as well as PCIe mode, I suppose), rx_std_signaldetect is stuck on logic ’1′, so enabling this signal alone isn’t enough.

But the real problem is that the signal detector is probably not good for anything but detecting SATA’s OOB: When I selected SATA mode, I did get some response on rx_std_signaldetect, but it was clearly not detecting the LFPS activity in a useful way. Unlike Cyclone V’s signal detector, Cyclone 10′s detector barely responded at all to a 31.25 MHz LFPS, and the detections occurred with pretty arbitrary timing, often with a pulse when the LFPS signal stopped, and some other random pulses as the wires went into electrical idle. In short, far from the desired assertion when the LFPS signals starts and deassertion when it stops.

Things got better as the toggling frequency increased, and around 125 MHz the assertion of the signal detect was steadily aligned with the onset of the LFPS toggling, however the deassertion was often delayed after the LFPS stopped. So even if the LFPS signal could be guaranteed to be at this frequency (it can’t, as it’s produced by the USB 3.x link partner, and 125 MHz is above maximum) the issue with the deassertion makes it impossible to use it with LFPS, which is extremely sensitive to the timing of onset and release of the toggling.

In fact, it’s probably useless for PCIe as well, as a PCIe beacon is allowed between 30 kHz – 500 MHz. This might explain why recent version of user guides for PCIe block for Cyclone V, Arria V, Cyclone 10 and Arria 10, had this sentence added:

These IP cores also do not support the in-band beacon or sideband WAKE# signal, which are mechanisms to signal a wake-up event to the upstream device.

The problem was probably not spotted for a while because the beacon is rarely used: The PCIe spec utilizes beacon transmission only from a device towards the host (upstream) for the sake of bringing up the link from a low power state. So signal detection by an FPGA for the sake of PCIe is only required when the FPGA acts as a host, and low-power modes are supported. In short, practically never.

What’s left? SATA. That will probably work, because the differential wires toggle rapidly, and it doesn’t matter so much if the detection is a bit off-beat.

So I resorted to detecting the LFPS bursts directly from uncoded received data, rather than using the signal detect feature. The rest of this post relates to my attempts before I gave up.

The options are limited

Quartus is going a long way to be “helpful” by verifying that the parameter assignments make sense with regards to the intended protocol (e.g. SATA, PCIe etc), as reflected by the “prot_mode” parameter. This often means that the fitter throws an error when one tries to alter a parameter from its default. It’s like someone said nope, if you’re using the transceiver for SATA, these and these are the correct analog parameters for the PMA, and if you try otherwise, the fitter will kick your bottom for your own protection.

Or maybe it’s a gentle way of telling us users not to try anything but the protocols for which the transceiver is directly intended for.

The fitter may also ignore assignments because they were assinged an unrelated entity (e.g. to gtx_tx instead of the positive-signal’s name, gtx_txp). So be always sure to look in the fitter report’s “Ignored Assignments” section.

One could speculate that this nanny-like behavior can be disabled by setting one of the pma_*_sup_mode parameters to “engineering_mode” rather than the default “user_mode”, but see below on this.

QSF

I expected to solve this by turning the parameters of the signal detector like I’ve previously done with Cyclone V: By virtue of assignments of QSF file.

So here’s the catch: The User Guide also says that the signal detect threshold can be set by virtue of .qsf assignments, but none such are documented in the it (as of the version for Quartus 20.1), and the Assignment Editor offers no parameter of this sort.

For Cyclone V, it’s documented in the V-Series Transceiver PHY IP Core User Guide around page 20-31, and there are recommended values on this page. My anecdotal experiments seem to indicate that assigning XCVR_* attributes (without the C10 part) to a Cyclone 10 transceiver is accepted however ignored by Quartus. In other words, trying to use Cyclone-V QSF assignments won’t cut.

So let’s start the guesswork on the names of the QSF parameters.

Hint source I: The fitter report

The fitter report has a section called “Receiver Channel”, which shows the attributes of the transceiver’s components as applied de-facto. Among others, there’s a part saying

;             -- Name                                           ; frontend_ins|xcvr_inst|xcvr_native_a10_0|xcvr_native_a10_0|g_xcvr_native_insts[0].twentynm_xcvr_native_inst|twentynm_xcvr_native_inst|inst_twentynm_pma|gen_twentynm_hssi_pma_rx_sd.inst_twentynm_hssi_pma_rx_sd                               ;
;             -- Location                                       ; HSSIPMARXSD_1D4                                                                                                                                                                                                                                                       ;
;         -- Advanced Parameters                                ;                                                                                                                                                                                                                                                                       ;
;             -- link                                           ; mr                                                                                                                                                                                                                                                                    ;
;             -- power_mode                                     ; mid_power                                                                                                                                                                                                                                                             ;
;             -- prot_mode                                      ; sata_rx                                                                                                                                                                                                                                                               ;
;             -- sd_output_off                                  ; 1                                                                                                                                                                                                                                                                     ;
;             -- sd_output_on                                   ; 1                                                                                                                                                                                                                                                                     ;
;             -- sd_pdb                                         ; sd_on                                                                                                                                                                                                                                                                 ;
;             -- sd_threshold                                   ; sdlv_3

It’s actually recommended to go through this part in the fitter report in any case, to make sure it was set up as desired.

But this part allows guessing the names of the parameters for the QSF file. For example, the following assignments are perfectly legal (and match the setting shown above):

set_instance_assignment -name XCVR_C10_RX_SD_OUTPUT_OFF 1 -to gtx_rxp
set_instance_assignment -name XCVR_C10_RX_SD_OUTPUT_ON 1 -to gtx_rxp
set_instance_assignment -name XCVR_C10_RX_SD_THRESHOLD SDLV_3 -to gtx_rxp

It doesn’t take a cyber hacker to see the connection between the QSF parameter names and those appearing in the report. This works for some parameters, and not for other. But this is the easiest way to guess parameter names.

Hint source II: Read the sources

But what’s the allowed values that can be assigned to these parameters? Hints on that can be obtained from the System Verilog files generated for the IP, in particular the one named xcvr_xcvr_native_a10_0_altera_xcvr_native_a10_171_ev4uzpa.sv (the “ev4uzpa” suffix varies), which has a section going:

parameter pma_rx_sd_prot_mode = "basic_rx",//basic_kr_rx basic_rx cpri_rx gpon_rx pcie_gen1_rx pcie_gen2_rx pcie_gen3_rx pcie_gen4_rx qpi_rx sata_rx unused
parameter pma_rx_sd_sd_output_off = 1,//0:28
parameter pma_rx_sd_sd_output_on = 1,//0:15
parameter pma_rx_sd_sd_pdb = "sd_off",//sd_off sd_on
parameter pma_rx_sd_sd_threshold = 3,//0:15
parameter pma_rx_sd_sup_mode = "user_mode",//engineering_mode user_mode

Note the comments, saying which values are allowed for each parameter. On a good day, staying within these value ranges makes the tools accept the assignments, and on an even better day, the fitter won’t throw an error because it considers the values unsuitable.

It’s worth taking a look on the other modules as well, even though they’re likely to have the same comments.

As far as I’ve seen, these parameters are set by the toplevel module for the transceiver IP. QSF assignments, if present, override the former.

Hint source III: What do these assignments mean?

For this I suggest looking at ip/altera/alt_xcvr/alt_xcvr_core/nd/doc/PMA_RegMap.csv (or similar file name) under the root directory of the Quartus installation. Yes, we’re digging in Quartus’ backyard now. I found these files by searching for strings in all files of the Quartus installation. Reverse engineering, after all.

In fact, I’m not sure if this is the correct file to look at, or maybe CR2_PMA_RegMap.csv or whatever. Neither do I know what they mean exactly. It’s however quite evident that these CSV files (opens with your favorite spreadsheet application) were intended to document the register space of the PMA. But the table that shows has a “Attribute Description” column with a few meaningful words on each attribute as well as a column named “Attribute Encoding”, which may happen to be the value to use in a QSF assignment (may and may not work).

There’s also an official register map from Intel available for download, named c10-registermap-official.xlsx, which apparently contains complimentary information. But it’s not possible to deduce QSF names from this file.

Hint source IV: What assignments are legal, then?

I mentioned earlier that the fitter rejects certain value assignment because they apparently don’t make sense. The rules seem to be written as a Tcl script in ip/altera/alt_xcvr/alt_xcvr_core/nd/tcl/ct2_pma_rx_sd_simple.tcl (and similar). Once again, under the Quartus installation root. And yet once again, sometimes this helps, and sometimes it doesn’t.

Hint source V: The names of the QSF paramaters

Up to this point, the names of the parameters to assign in the QSF file were a matter of speculation, based upon similar names in other contexts.

It’s possible to harvest all possible names by searching for strings in one of Quartus’ installed binaries, as shown on this post for non-Pro Quartus 17.1, and this post for Quartus Pro 19.2.

For a complete list of allowed QSF assignment that relate to Cyclone 10 transceivers (or so I groundlessly believe), search for strings in libdb_acf.so, e.g.

$ strings ./quartus/linux64/libdb_acf.so | grep XCVR_C10 | sort
XCVR_C10_CDR_PLL_ANALOG_MODE
XCVR_C10_CDR_PLL_POWER_MODE
XCVR_C10_CDR_PLL_REQUIRES_GT_CAPABLE_CHANNEL
XCVR_C10_CDR_PLL_UC_RO_CAL
XCVR_C10_CMU_FPLL_ANALOG_MODE
XCVR_C10_CMU_FPLL_PLL_DPRIO_CLK_VREG_BOOST
XCVR_C10_CMU_FPLL_PLL_DPRIO_FPLL_VREG1_BOOST
XCVR_C10_CMU_FPLL_PLL_DPRIO_FPLL_VREG_BOOST
XCVR_C10_CMU_FPLL_PLL_DPRIO_STATUS_SELECT
XCVR_C10_CMU_FPLL_POWER_MODE
XCVR_C10_LC_PLL_ANALOG_MODE
XCVR_C10_LC_PLL_POWER_MODE
XCVR_C10_PM_UC_CLKDIV_SEL
XCVR_C10_PM_UC_CLKSEL_CORE
XCVR_C10_PM_UC_CLKSEL_OSC
XCVR_C10_REFCLK_TERM_TRISTATE
XCVR_C10_RX_ADAPT_DFE_CONTROL_SEL
XCVR_C10_RX_ADAPT_DFE_SEL
XCVR_C10_RX_ADAPT_VGA_SEL
XCVR_C10_RX_ADAPT_VREF_SEL
XCVR_C10_RX_ADP_CTLE_ACGAIN_4S
XCVR_C10_RX_ADP_CTLE_EQZ_1S_SEL
XCVR_C10_RX_ADP_DFE_FLTAP_POSITION
XCVR_C10_RX_ADP_DFE_FXTAP1
XCVR_C10_RX_ADP_DFE_FXTAP10
XCVR_C10_RX_ADP_DFE_FXTAP10_SGN
XCVR_C10_RX_ADP_DFE_FXTAP11
XCVR_C10_RX_ADP_DFE_FXTAP11_SGN
XCVR_C10_RX_ADP_DFE_FXTAP2
XCVR_C10_RX_ADP_DFE_FXTAP2_SGN
XCVR_C10_RX_ADP_DFE_FXTAP3
XCVR_C10_RX_ADP_DFE_FXTAP3_SGN
XCVR_C10_RX_ADP_DFE_FXTAP4
XCVR_C10_RX_ADP_DFE_FXTAP4_SGN
XCVR_C10_RX_ADP_DFE_FXTAP5
XCVR_C10_RX_ADP_DFE_FXTAP5_SGN
XCVR_C10_RX_ADP_DFE_FXTAP6
XCVR_C10_RX_ADP_DFE_FXTAP6_SGN
XCVR_C10_RX_ADP_DFE_FXTAP7
XCVR_C10_RX_ADP_DFE_FXTAP7_SGN
XCVR_C10_RX_ADP_DFE_FXTAP8
XCVR_C10_RX_ADP_DFE_FXTAP8_SGN
XCVR_C10_RX_ADP_DFE_FXTAP9
XCVR_C10_RX_ADP_DFE_FXTAP9_SGN
XCVR_C10_RX_ADP_LFEQ_FB_SEL
XCVR_C10_RX_ADP_ONETIME_DFE
XCVR_C10_RX_ADP_VGA_SEL
XCVR_C10_RX_ADP_VREF_SEL
XCVR_C10_RX_BYPASS_EQZ_STAGES_234
XCVR_C10_RX_EQ_BW_SEL
XCVR_C10_RX_EQ_DC_GAIN_TRIM
XCVR_C10_RX_INPUT_VCM_SEL
XCVR_C10_RX_LINK
XCVR_C10_RX_OFFSET_CANCELLATION_CTRL
XCVR_C10_RX_ONE_STAGE_ENABLE
XCVR_C10_RX_POWER_MODE
XCVR_C10_RX_QPI_ENABLE
XCVR_C10_RX_RX_SEL_BIAS_SOURCE
XCVR_C10_RX_SD_OUTPUT_OFF
XCVR_C10_RX_SD_OUTPUT_ON
XCVR_C10_RX_SD_THRESHOLD
XCVR_C10_RX_TERM_SEL
XCVR_C10_RX_TERM_TRI_ENABLE
XCVR_C10_RX_UC_RX_DFE_CAL
XCVR_C10_RX_VCCELA_SUPPLY_VOLTAGE
XCVR_C10_RX_VCM_CURRENT_ADD
XCVR_C10_RX_VCM_SEL
XCVR_C10_RX_XRX_PATH_ANALOG_MODE
XCVR_C10_TX_COMPENSATION_EN
XCVR_C10_TX_DCD_DETECTION_EN
XCVR_C10_TX_DPRIO_CGB_VREG_BOOST
XCVR_C10_TX_LINK
XCVR_C10_TX_LOW_POWER_EN
XCVR_C10_TX_POWER_MODE
XCVR_C10_TX_PRE_EMP_SIGN_1ST_POST_TAP
XCVR_C10_TX_PRE_EMP_SIGN_2ND_POST_TAP
XCVR_C10_TX_PRE_EMP_SIGN_PRE_TAP_1T
XCVR_C10_TX_PRE_EMP_SIGN_PRE_TAP_2T
XCVR_C10_TX_PRE_EMP_SWITCHING_CTRL_1ST_POST_TAP
XCVR_C10_TX_PRE_EMP_SWITCHING_CTRL_2ND_POST_TAP
XCVR_C10_TX_PRE_EMP_SWITCHING_CTRL_PRE_TAP_1T
XCVR_C10_TX_PRE_EMP_SWITCHING_CTRL_PRE_TAP_2T
XCVR_C10_TX_RES_CAL_LOCAL
XCVR_C10_TX_RX_DET
XCVR_C10_TX_RX_DET_OUTPUT_SEL
XCVR_C10_TX_RX_DET_PDB
XCVR_C10_TX_SLEW_RATE_CTRL
XCVR_C10_TX_TERM_CODE
XCVR_C10_TX_TERM_SEL
XCVR_C10_TX_UC_DCD_CAL
XCVR_C10_TX_UC_GEN3
XCVR_C10_TX_UC_GEN4
XCVR_C10_TX_UC_SKEW_CAL
XCVR_C10_TX_UC_TXVOD_CAL
XCVR_C10_TX_UC_TXVOD_CAL_CONT
XCVR_C10_TX_UC_VCC_SETTING
XCVR_C10_TX_USER_FIR_COEFF_CTRL_SEL
XCVR_C10_TX_VOD_OUTPUT_SWING_CTRL
XCVR_C10_TX_XTX_PATH_ANALOG_MODE

So yes, now we’re looking for strings in a binary file.

And yet, all this doesn’t necessarily help

With all these hints, there’s still some pure guesswork. For example, I tried

set_instance_assignment -name XCVR_C10_RX_SD_OUTPUT_ON 3 -to gtx_rxp

and the fitter gave me

    Error (15744): The settings must match one or more of these conditions:
    Error (15744): ( sup_mode == ENGINEERING_MODE ) OR ( prot_mode != SATA_RX ) OR ( sd_output_on == DATA_PULSE_6 )
    Error (15744): But the following assignments violate the above conditions:
    Error (15744): sup_mode = USER_MODE
    Error (15744): prot_mode = SATA_RX
    Error (15744): sd_output_on = DATA_PULSE_10 -- Set by Pin Assignment "XCVR_A10_RX_SD_OUTPUT_ON" (QSF Name "XCVR_A10_RX_SD_OUTPUT_ON")

So first, let’s notice that it blames a XCVR_A10_* assignment, even though I used a XCVR_C10_* assignment in the QSF file. Really.

Also note the hint that setting sup_mode to ENGINEERING_MODE would have let us off the hook. More on that below (however don’t expect much).

But how did the assigning XCVR_C10_RX_SD_OUTPUT_ON with the integer 3 turn into DATA_PULSE_10? Maybe look in PMA_RegMap.csv, mentioned above? But no, DATA_PULSE_10 is assigned 5′b01110 as a value to write to a register, and it’s the 5th value listed, so no matter if you count from zero or one, 3 is not the answer.

Maybe ct2_pma_rx_sd_simple.tcl, also mentioned above? That helps even less, as there’s no sign there that DATA_PULSE_10 would be special. In short, just play with the integer value until hitting gold. Or even better, don’t assign anything, and use the default.

Likewise, setting

set_instance_assignment -name XCVR_C10_RX_SD_OUTPUT_OFF 6 -to gtx_rxp

yields

    Error (15744): In atom 'frontend_ins|xcvr_inst|xcvr_native_a10_0|xcvr_native_a10_0|g_xcvr_native_insts[0].twentynm_xcvr_native_inst|twentynm_xcvr_native_inst|inst_twentynm_pma|gen_twentynm_hssi_pma_rx_sd.inst_twentynm_hssi_pma_rx_sd'
    Error (15744): The settings must match one or more of these conditions:
    Error (15744): ( sup_mode == ENGINEERING_MODE ) OR ( prot_mode != SATA_RX ) OR ( sd_output_off == CLK_DIVRX_2 )
    Error (15744): But the following assignments violate the above conditions:
    Error (15744): sup_mode = USER_MODE
    Error (15744): prot_mode = SATA_RX
    Error (15744): sd_output_off = CLK_DIVRX_7 -- Set by Pin Assignment "XCVR_A10_RX_SD_OUTPUT_OFF" (QSF Name "XCVR_A10_RX_SD_OUTPUT_OFF")

Attempting to enable Engineering Mode

Since ENGINEERING_MODE is often mentioned in the fitter’s error messages, I thought maybe enabling it could silence these errors and allow wider options. For example, I attempted to enable the Electrical Idle state on the transmission wires on a non-PCIe transciever by editing one of the files generated by the transceiver IP tools (xcvr_xcvr_native_a10_0.v), changing the line saying

.hssi_tx_pcs_pma_interface_bypass_pma_txelecidle("true"),

to

.hssi_tx_pcs_pma_interface_bypass_pma_txelecidle("false"),

but the fitter threw the following error:

    Error (15744): In atom 'xcvr_inst|xcvr_native_a10_0|xcvr_native_a10_0|g_xcvr_native_insts[0].twentynm_xcvr_native_inst|twentynm_xcvr_native_inst|inst_twentynm_pcs|gen_twentynm_hssi_tx_pcs_pma_interface.inst_twentynm_hssi_tx_pcs_pma_interface'
    Error (15744): The settings must match one or more of these conditions:
    Error (15744): ( sup_mode == ENGINEERING_MODE ) OR ( bypass_pma_txelecidle == TRUE ) OR ( pcie_sub_prot_mode_tx != OTHER_PROT_MODE )
    Error (15744): But the following assignments violate the above conditions:
    Error (15744): sup_mode = USER_MODE
    Error (15744): bypass_pma_txelecidle = FALSE
    Error (15744): pcie_sub_prot_mode_tx = OTHER_PROT_MODE

So it tells me that if I want bypass_pma_txelecidle as “false” I have to either set pcie_sub_prot_mode_tx to one of the PCIe modes, or set sup_mode to ENGINEERING_MODE. Changing pcie_sub_prot_mode_tx is out of the question, because the only way to settle the conflicts reported by the fitter is to turn the entire transceiver to follow the predefined PCIe settings. Had I been able to go that path, I would have done that long ago.

So switch to Engineering Mode, whatever that means, by editing the same file, changing

.hssi_tx_pcs_pma_interface_sup_mode("user_mode"),

to

.hssi_tx_pcs_pma_interface_sup_mode("engineering_mode"),

but the fitter really didn’t like that:

    Error (15744): In atom 'xcvr_inst|xcvr_native_a10_0|xcvr_native_a10_0|g_xcvr_native_insts[0].twentynm_xcvr_native_inst|twentynm_xcvr_native_inst|inst_twentynm_pcs|gen_twentynm_hssi_tx_pcs_pma_interface.inst_twentynm_hssi_tx_pcs_pma_interface'
    Error (15744): The settings must match one or more of these conditions:
    Error (15744): ( sup_mode OR ( sup_mode == USER_MODE )
    Error (15744): But the following assignments violate the above conditions:
    Error (15744): sup_mode = ENGINEERING_MODE

This is somewhat cryptic, because it implies that sup_mode could just evaluate “true” in some way. Anyhow, selecting ENGINEERING_MODE was rejected flat, so that’s not an option for us regular people. There’s probably some secret sauce method to allow this, but that goes beyond what is sensible to work around the tools’ restrictions.

Conclusion

Setting up a Cyclone 10 transceiver for a use other than specifically intended by the FPGA’s vendor is a visit to nomansland. Reverse engineering Quartus does help to some extent, but some issues are left to guessing.

And the transceiver itself appears to be a step backwards compared with the series-V FPGAs. It may reach higher rates, but that came at a cost. Or maybe it’s related to the different silicon process. This way or another, it’s not all that impressive.

Add a Comment

required, use real name
required, will not be published
optional, your blog address