Skip to content

Latest commit

 

History

History
250 lines (165 loc) · 22.2 KB

File metadata and controls

250 lines (165 loc) · 22.2 KB

20 Feb 2025

Previous journal: Next journal:
0225-2025-01-12.md 0227-2025-02-25.md

tt06-grab-bag Analog VGA works!

Note

Watch my video segment on Matt Venn's TT06 Bring-up Party if you'd like a quick overview, then come back here for more details.

Video projects on Tiny Tapeout custom ASICs (custom silicon chips) have typically been limited to digital 6-bit colour (RGB222, or 64 distinct colours). Some have gone up to 12-bit colour (RGB444, or 4096 distinct colours) but this uses a lot of the limited IO pins. I wanted to create a design that could potentially drive more like 18-bit (RGB666) or ideally 24-bit colour (RGB888, millions of colours) by using 3 analog pins only. My Tiny Tapeout 6 submission, "TT06 Grab Bag", gets close to accomplishing that.

tt06-grab-bag prototype PCB working

In April 2024 I submitted "TT06 Grab Bag" (GitHub link) -- my first analog VLSI design -- to Tiny Tapeout 6 (as part of the Zero to ASIC Analog Course) and after about 9 months of waiting for the fabricated chip to arrive I've been able to prove that the design works pretty well as intended: It generates high-colour-depth 640x480 VGA output by producing on-chip analog video signals.

The design implements a simple VGA driver demonstration that generates analog outputs for each of the Red, Green, and Blue channels. It does this using 3× RDACs (resistive DACs, i.e. using an R2R ladder). It's a mixed-signal design in that it includes a digital control block that can be directed to produce a few different test patterns as 24-bit outputs (8 bits per colour channel) and feeds them into the on-chip R2R ladders.

Due to Tiny Tapeout limitations, my project relies on external op-amps to buffer the analog outputs, and also to scale their 0-1.8V range down to 0-0.7V.

From simulation, the design was expected to be able to transition (i.e. 'slew') between maximum and minimum brightness within about 240ns (where, using a 25MHz VGA 'dot clock', 40ns is 1 pixel horizontally, so 240ns is 6 pixels horizontally). Due to a much-improved external amp solution this slew rate was reduced to no worse than about 100ns (at the cost of some extra noise, but which is hard to see anyway).

Analog schematics

Combined schematics for VGA DACs

I have a testbench schematic for both the ideal and extracted layout (view online here).

I started with a simple 8-bit R2R DAC schematic inspired by Matt Venn's example and sized the resistors to be R=10k (instead of 22.65k), and to ensure they wouldn't typically exceed the sky130-recommended 100µA/µm typical current. This means they should have lower impedance and hence can get a faster full-swing.

Digital block

I used OpenLane to independently harden a simple digital control block implemented in Verilog. This produced a GDS macro that I was then later able to place into the Tiny Tapeout analog template and wire up with the digital IOs, the power rails, and with my analog layout blocks.

Digital block GDS over a Verilog snippet

The digital block generates all the VGA timing (including HSYNC and VSYNC) from a 25.175MHz clock source, though for simplicity I assume 25.000MHz (which is OK too) and thus 40ns per pixel. Besides doing external RGB222 output via uo_out to be compatible with the Tiny VGA PMOD, it also generates internal digital signals that are 8bpp (bits per pixel) per each of the RGB (Red, Green, Blue) colour channels that become the inputs to 3 instances of the DACs.

The specific test pattern generated by the digital block is selected by the 8 ui_in pins (as reset is released), and some suggested combinations (in binary, MSB to LSB) include:

  • 00010000: Colour ramps, with red ramping per pixel, green per line, and blue per frame.
  • 00010001: As above, but in green, blue, red order.
  • 00010010: As above, but in blue, red, green order.
  • 00010011: All 3 channels ramp per-pixel, producing grey ramps (good for comparing consistency between channels).
  • 00110000: Pretty XOR patterns on red and green, with a angled gradiated pattern wiping across blue.
  • 00000000: Pass-through: Any value subsequently presented on ui_in becomes the direct/live 8-bit input to all 3 DACs.

There is also an option (i.e. when ui_in[7] is 1) to generate timing compatible with a 1440x900 VGA display, given a 26.6175MHz clock instead of 25MHz. This one quarter of the normal clock for this resolution but should allow a compatible monitor to expose more circuit glitches and artifacts (if there are any).

Layout, extraction, LVS

I created my layouts in Magic by exporting a suitable SPICE netlist from Xschem and then importing, to create pcells.

My first layout was actually for a basic CMOS inverter, just to prove I could get the flow right (including LVS).

Before creating the layout, I at least simulated it (see below) to make sure it behaved reasonably well.

I then created the layout of a single R2R DAC cell, and placed 3 instances of it beside the digital block's GDS, and wired everything up. I also had spare area so I included a 4th instance and just used the upper 4 bits of it connected to 4 spare digital inputs (and the 4 LSBs of that DAC connected to GND).

Completed render of layout in Magic

Amazingly, I got LVS to pass pretty quickly, which was reassuring. A Verilog black-box model was used to define all instances of the DACs and their connections, as the design's top-level "schematic" (netlist) input for LVS.

I then used Matt Venn's example Makefile to do parasitic extraction of a SPICE netlist from the layout, and I was able to simulate it (see below).

Simulation

I did several different kinds of simulation including:

  • Simple ngspice simulation, set up via Xschem, of a single DAC driven by a ramping digital input, and with a model of the TT06 analog pin capacitive loading -- very fast. See here.
  • Simulation of the DAC layout following parasitic extraction -- fast. If it helps, see the Make target for extracting PEX SPICE for simulation.
  • Cosimulation in ngspice, with a Verilator backend simulating the digital part and using it to stimulate 3 instances of the extracted DAC layout -- fast. Got some introductory notes on it.
  • Simulation of the full layout extraction, including the entire digital block's circuit. This is extremely slow (though I did manage to speed it up a little, and there's more I could possibly do). Arguably it's the most accurate, and it was even able to expose some (expected) non-linearities. I have notes on my initial attempts with this and then identified simulation glitches which I worked around and got some good results.

Note that I think my more-recent simulation commits were done in a different repo, following 2024-05-11.

Full-layout extracted circuit simulation

Full-layout extraction and full-circuit simulation was done after the submission closed and it revealed a possible non-linearity in the DAC output: Notice that the rising lines are not perfectly straight, and this (or at least something similar) became apparent in the real chip too. I suspect this is because OpenLane sized the output buffer cells differently for certain bits going into the DACs; I should've had my own buffers on the DAC inputs.

When doing any simulations, I made sure to write to files that could then be rendered as a VGA image preview. This is easy for the purely-digital simulation with Verilator, where the signal is a pure 24-bit digital output, but I also wanted a more-realistic analog simulation:

Perfect digital vs. analog XOR render

To render a PNG file representing the VGA image produced by the analog simulation results, I wrote the data to an ngspice .raw file and created a render.py script to help with parsing the data and doing the rendering.

VGA amp board

Simulation showed I should expect a slew rate of about 240ns on the analog output pins. This stands to reason as:

  • The expected TT pin load is 500R and 5pF. Impedance of DAC is ~10k. Hence, τ = RC = ~52ns.
  • It takes 5τ to get to about 99% of the target voltage, which is ~262ns.

VGA has a 75Ω load impedance per channel, with a voltage range of 0V (black) to 0.7V (full brightness). Tiny Tapeout can't provide enough current for this load: it would be about 9.3mA per channel, but TT's analog mux/pins can support only up to about 4mA per channel, and the whole project is limited to about 10mA anyway.

My on-chip circuit produces analog outputs that are 0-1.8V and about 10k impedance by virtue of the R2R DAC with R=10k, and so I made an external op-amp board to buffer the signal and do the level shifting. I used an OPA3355 triple video op-amp chip and followed its application notes. Though it worked, the results weren't great on this rough hand-made solution:

VGA amp protoboard

With lots of great tips from Sylvain Munaut I was able to make a much better board:

  • Getting rid of U.FL/coaxial cables; they add a lot of extra capacitive load. I replaced them with simple short wires.
  • Using another OPA3355-based prototyping PCB I'd had fabbed for this project, which arrived shortly after my initial experiments.
  • Using a 2.2k pull-down resistor on the op-amp input side to effectively get lower impedance and hence a faster slew rate from the ASIC (despite a lower output voltage range and hence more noise).
  • Forgetting about biasing (which I was doing to try and get around rail-to-rail limitations with the OPA3355) -- turns out it's pretty good and I don't care much about the near-0V where it bottoms out.
  • In turn, this meant I could use a simple impedance-matched feedback network to set the gain on the now-lower output range, to get it up to 0-0.7V and 75Ω impedance.

Schematic of one channel opamp configuration

Note also that my scope probe introduced a lot of extra load on the input-side signals when I was trying to measure them.

Here's the revised board wired directly to the TT06 chip carrier (aka "breakout") board:

tt06-grab-bag prototype PCB setup

In any case, after reworking the boards I had on hand, I was able to get the slews right down. This scope screenshot shows the unmodified CH2 (blue) with ~800ns slew, compared with the improved CH1 (yellow) at about ~68ns (though I was later getting closer to about 100ns):

Channel slews compared on oscilloscope

Comparing the screen captures from the original amp board and the new amp board shows the notable improvement in quality:

Smeared original capture vs. improved new amp capture

NOTE: Screen captures were produced in a way that would have degraded the image quality further:

  • VGA output from the amp board goes into a cheap VGA-to-HDMI converter
  • This is fed into an HDMI capture device (Elgato Cam Link)
  • This then captures via OBS to an MP4 file

Where I was able to do so, I tried to control all stages for minimal quality reductions, but some can't be controlled at all.

Interposer board

For a nicer and more-permanent solution with (hopefully) even better performance and no hacking of the TT06 carrier board, I created an "interposer board" (aka "sandwich board"); view schematics online. It slots in between the TT06 carrier and TT06 demo boards, and implements the ideal op-amp circuit and provides the VGA connector directly and with a better layout.

Interposer board design

Its main features are:

  • It takes 3.3V power from the vdda_in pin of the TT06 demo board; this supply is for the OPA3355 op-amp, and for the optional HSYNC/VSYNC buffer -- the latter of which I didn't bother populating and instead bypassed because it doesn't seem to be needed for modern monitors, though I did put 1k resistors in series for both so no more than ~3.3mA is drawn from the chip per digital output pin.
  • It redirects the analog pins that are used by my design, so they don't connect to the base demo board.
  • There are jumper pins allowing for the RGB chip outputs to be remapped to VGA channel inputs.
  • It's all neatly integrated, and includes a few other test/debug footprints if required.

I tested it with a 2nd (unmodified) TT06 board and it works like a charm.

Interposer board in operation

The actual performance is about the same, i.e. on the order of 100ns slew rate. Below are two channels switching from max-to-min and min-to-max at the same time:

Interpower slew rate on oscilloscope

Notice also that the lowest video level is about 80-100mV; I'm guessing this is the "rail-to-rail" limit of the single-supply op-amps. This means the darkest tones are crushed, but it still looks good on screen. I wouldn't be surprised if the monitor I'm using has automatic gain control too.

Non-linearities

The output from the DACs has some non-linearity, in that there are some discontinuities/"bumps" where there should otherwise be a perfect slope. These were seen in simulation and demonstrated in silicon.

DAC non-linearities in simulation and physical ASIC

Exposed above are a simluation prediction of discontinuities ("non-monotonicity") where both the red and the blue channel outputs are not completely straight lines, but they also differ from each other after the 50% level. In the actual physical ASIC this was measured also, and theses issues appear as banding in what should otherwise be completely smooth gradients with no other "features" visible:

Banding errors seen in smooth gradients

For more information on DAC errors (and any data conversion errors), see: https://www.analog.com/media/en/technical-documentation/technical-articles/adi-data-conversion.pdf

Some reasons these errors could occur include parasitics introduced by my specific layout, sizing of resistors (and how accurately Magic represented them between the "R" size and the "2R" size), how accurate the actual fabricated resistors are compared to each other (i.e. how well they are "matched"), and possibly even other coupling effects.

I was able to verify, however, that probably the main reason for these errors is that OpenLane selected different types of output buffers (and hence different drive strengths) for each of the 24 digital output bits going to the DACs. This was confirmed when I checked the original post-synthesis/layout Verilog netlist for the sky130 standard cells that drive each of the 24 digital colour outputs (that become the DACs' inputs). I had saved a copy of the original digital controller block's OpenLane hardening results in the repo and I searched the netlist to find inconsistencies. Here's the raw data:

$ egrep -H -n "X.\b[rgb]\b" ./results/final/verilog/gl/controller.v -B 5 | fgrep -v '.V'
./results/final/verilog/gl/controller.v-7637- sky130_fd_sc_hd__buf_2 output12 (.A(net12),
./results/final/verilog/gl/controller.v:7642:    .X(b[0]));
./results/final/verilog/gl/controller.v-7643- sky130_fd_sc_hd__buf_2 output13 (.A(net13),
./results/final/verilog/gl/controller.v:7648:    .X(b[1]));
./results/final/verilog/gl/controller.v-7649- sky130_fd_sc_hd__buf_2 output14 (.A(net14),
./results/final/verilog/gl/controller.v:7654:    .X(b[2]));
./results/final/verilog/gl/controller.v-7655- sky130_fd_sc_hd__buf_2 output15 (.A(net15),
./results/final/verilog/gl/controller.v:7660:    .X(b[3]));
./results/final/verilog/gl/controller.v-7661- sky130_fd_sc_hd__clkbuf_4 output16 (.A(net16),
./results/final/verilog/gl/controller.v:7666:    .X(b[4]));
./results/final/verilog/gl/controller.v-7667- sky130_fd_sc_hd__clkbuf_4 output17 (.A(net17),
./results/final/verilog/gl/controller.v:7672:    .X(b[5]));
./results/final/verilog/gl/controller.v-7673- sky130_fd_sc_hd__buf_2 output18 (.A(net18),
./results/final/verilog/gl/controller.v:7678:    .X(b[6]));
./results/final/verilog/gl/controller.v-7679- sky130_fd_sc_hd__clkbuf_4 output19 (.A(net19),
./results/final/verilog/gl/controller.v:7684:    .X(b[7]));
--
./results/final/verilog/gl/controller.v-7697- sky130_fd_sc_hd__buf_2 output22 (.A(net22),
./results/final/verilog/gl/controller.v:7702:    .X(g[0]));
./results/final/verilog/gl/controller.v-7703- sky130_fd_sc_hd__buf_2 output23 (.A(net23),
./results/final/verilog/gl/controller.v:7708:    .X(g[1]));
./results/final/verilog/gl/controller.v-7709- sky130_fd_sc_hd__buf_2 output24 (.A(net24),
./results/final/verilog/gl/controller.v:7714:    .X(g[2]));
./results/final/verilog/gl/controller.v-7715- sky130_fd_sc_hd__buf_2 output25 (.A(net25),
./results/final/verilog/gl/controller.v:7720:    .X(g[3]));
./results/final/verilog/gl/controller.v-7721- sky130_fd_sc_hd__clkbuf_4 output26 (.A(net26),
./results/final/verilog/gl/controller.v:7726:    .X(g[4]));
./results/final/verilog/gl/controller.v-7727- sky130_fd_sc_hd__buf_2 output27 (.A(net27),
./results/final/verilog/gl/controller.v:7732:    .X(g[5]));
./results/final/verilog/gl/controller.v-7733- sky130_fd_sc_hd__buf_2 output28 (.A(net28),
./results/final/verilog/gl/controller.v:7738:    .X(g[6]));
./results/final/verilog/gl/controller.v-7739- sky130_fd_sc_hd__buf_2 output29 (.A(net29),
./results/final/verilog/gl/controller.v:7744:    .X(g[7]));
--
./results/final/verilog/gl/controller.v-7769- sky130_fd_sc_hd__buf_2 output34 (.A(net34),
./results/final/verilog/gl/controller.v:7774:    .X(r[0]));
./results/final/verilog/gl/controller.v-7775- sky130_fd_sc_hd__buf_2 output35 (.A(net35),
./results/final/verilog/gl/controller.v:7780:    .X(r[1]));
./results/final/verilog/gl/controller.v-7781- sky130_fd_sc_hd__buf_2 output36 (.A(net36),
./results/final/verilog/gl/controller.v:7786:    .X(r[2]));
./results/final/verilog/gl/controller.v-7787- sky130_fd_sc_hd__buf_2 output37 (.A(net37),
./results/final/verilog/gl/controller.v:7792:    .X(r[3]));
./results/final/verilog/gl/controller.v-7793- sky130_fd_sc_hd__clkbuf_4 output38 (.A(net38),
./results/final/verilog/gl/controller.v:7798:    .X(r[4]));
./results/final/verilog/gl/controller.v-7799- sky130_fd_sc_hd__buf_2 output39 (.A(net39),
./results/final/verilog/gl/controller.v:7804:    .X(r[5]));
./results/final/verilog/gl/controller.v-7805- sky130_fd_sc_hd__buf_2 output40 (.A(net40),
./results/final/verilog/gl/controller.v:7810:    .X(r[6]));
./results/final/verilog/gl/controller.v-7811- sky130_fd_sc_hd__buf_2 output41 (.A(net41),
./results/final/verilog/gl/controller.v:7816:    .X(r[7]));

To summarize:

Red bit Red buffer Green bit Green buffer Blue bit Blue buffer
r[0] buf_2 g[0] buf_2 b[0] buf_2
r[1] buf_2 g[1] buf_2 b[1] buf_2
r[2] buf_2 g[2] buf_2 b[2] buf_2
r[3] buf_2 g[3] buf_2 b[3] buf_2
r[4] clkbuf_4 g[4] clkbuf_4 b[4] clkbuf_4
r[5] buf_2 g[5] buf_2 b[5] clkbuf_4
r[6] buf_2 g[6] buf_2 b[6] buf_2
r[7] buf_2 g[7] buf_2 b[7] clkbuf_4

Note that besides different clkbuf_4 cells in general, the Blue channel in particular is different from Red and Green. These can be located by viewing the GDS too:

GDS cells showing differences per channel bit

Stay tuned for TT08 VGA FUN!

I submitted a newer version of this idea to Tiny Tapeout 8, called "TT08 VGA FUN!". It has experiments with two different kinds of current-steering DACs (instead of RDACs) for hopefully better performance, and an improved digital control block. These chips are expected to go through initial testing in March 2025 and then maybe extensive testing 2 months later.

For more information, see 0216 (though there are many earlier journal entries I have on this also) and the original repo.