Mike Field's work showed how to achieve DVI output on an FPGA that has built-in TMDS outputs, or even LVDS outputs (at 720p over a 1.5m HDMI cable). The problem is that while the iCE40 FPGAs do have LVDS inputs, they do not actually have LVDS outputs. They can 'emulate' LVDS outputs using two LVCMOS outputs and three external resistors, that "should be surface mounted as close as possible to the FPGA output pins" - which would mean designing an entire custom FPGA board, not just a custom Pmod add-on!
So, in search of an alternative I started out by posting a question on reddit, and one answer led me to a helpful article (written over 15 years ago in 2003!) with a nice clear table outlining what I had been fruitlessly searching for - the electrical characteristics of CML!
Another answer mentioned Black Mesa Labs, who do have two designs for DVI Pmods - but they use RGB signalling and rely on a conversion chip (TFP410) to generate the digital DVI data (a chip which costs around five times more than the combined cost of the two chips I am using here), and they require 7 to 16 FPGA outputs depending on the colour depth - whereas my concept manually generates the DVI data and uses only 4 FPGA outputs regardless of colour depth.
I also found another project working towards DVI on an iCE40 using AC-coupling, but with no voltage-level conversion? The link from there to an application note discussing the use of AC-coupling when translating between single-ended and differential signalling was another important bread-crumb on the path though.
The design that I landed on was to use the fastest 3.3V to 1.2V level-shifter I could find (SN74AXC8T245), and then feed the 1.2V outputs into a dedicated DVI / HDMI level-shifter (PTN3366) to achieve proper TMDS / CML outputs. The single-ended to differential conversion happens by connecting the 1.2V signals (AC-coupled through capacitors) to the PTN3366's positive inputs, with the PTN3366's negative inputs (AC-coupled through capacitors) connected to ground. There is an LDO onboard to provide the 1.2V supply needed, and a step-up converter to provide the 5V required on the HDMI connector. There is a solder-jumper to optionally tie the connector's shield to ground, only because I had read so much conflicting information on whether it should be grounded or not.
The code is based on Mike Field's work and uses the iCE40's DDR output mode to get 250MHz outputs from a 125MHz clock. As a proof-of-concept, it works! The one monitor I have here with a HDMI input now displays the test pattern no problem (at first I was getting some weird interference / glitchy vertical lines, until I switched to using a short 0.5m HDMI cable) but my TV does not like it - a lot seems to depend on the tolerance of the screen being used...
Anyway, even though this is definitely right at the edge of what both the iCE40 and I seem capable of, I'm glad to have at least met the challenge set for myself!