├── .github ├── FUNDING.yml └── workflows │ └── testbench.yml ├── .gitignore ├── LICENSE-APACHE ├── LICENSE-MIT ├── Manifest.py ├── README.md ├── README_fr.md ├── demo.gif ├── requirements.txt ├── sim ├── audio_clock_tb │ └── Manifest.py ├── audio_param_tb │ └── Manifest.py ├── spd_tb │ └── Manifest.py ├── top_tb │ └── Manifest.py └── vsim.do ├── src ├── Manifest.py ├── audio_clock_regeneration_packet.sv ├── audio_info_frame.sv ├── audio_sample_packet.sv ├── auxiliary_video_information_info_frame.sv ├── hdmi.sv ├── packet_assembler.sv ├── packet_picker.sv ├── serializer.sv ├── source_product_description_info_frame.sv └── tmds_channel.sv ├── test ├── audio_clock_tb │ ├── Manifest.py │ └── audio_clock_tb.sv ├── audio_param_tb │ ├── Manifest.py │ └── audio_param_tb.sv ├── spd_tb │ ├── Manifest.py │ └── spd_tb.sv └── top_tb │ ├── Manifest.py │ ├── pll.sv │ └── top_tb.sv └── top ├── Manifest.py └── top.sv /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['sameer'] 4 | -------------------------------------------------------------------------------- /.github/workflows/testbench.yml: -------------------------------------------------------------------------------- 1 | name: hdmi 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | - uses: actions/setup-python@v2 13 | with: 14 | python-version: '3.x' 15 | - name: Install hdlmake 16 | run: pip install -r requirements.txt 17 | - name: Install ModelSim dependencies 18 | run: | 19 | sudo dpkg --add-architecture i386 20 | sudo apt-get update 21 | sudo apt-get install lib32z1 lib32stdc++6 libexpat1:i386 libc6:i386 libsm6:i386 libncurses5:i386 libx11-6:i386 zlib1g:i386 libxext6:i386 libxft2:i386 libgcc-s1:i386 22 | - name: Cache ModelSim 23 | uses: actions/cache@v2 24 | with: 25 | path: ~/intelFPGA/* 26 | key: ${{ runner.os }}-modelsim-20.1 27 | - name: Install ModelSim if not cached 28 | run: stat $HOME/intelFPGA/20.1/modelsim_ase || (curl -L 'https://downloads.intel.com/akdlm/software/acdsinst/20.1std.1/720/ib_installers/ModelSimSetup-20.1.1.720-linux.run' -o ModelSimSetup.run && chmod +x ModelSimSetup.run && ./ModelSimSetup.run --mode unattended --accept_eula 1 ) 29 | - name: Add ModelSim to PATH 30 | run: echo "$HOME/intelFPGA/20.1/modelsim_ase/bin" >> $GITHUB_PATH 31 | - name: Top Testbench 32 | run: cd $GITHUB_WORKSPACE/sim/top_tb/ && hdlmake fetch && hdlmake && make 33 | - name: Audio Param Testbench 34 | run: cd $GITHUB_WORKSPACE/sim/audio_param_tb/ && hdlmake fetch && hdlmake && make 35 | - name: Audio Clock Testbench 36 | run: cd $GITHUB_WORKSPACE/sim/audio_param_tb/ && hdlmake fetch && hdlmake && make 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .venv 3 | *.pin 4 | *.pof 5 | *.sof 6 | *.qpf 7 | *.qsf 8 | *.sid 9 | *.map.* 10 | *.sta.* 11 | *.fit.* 12 | bitstream.tcl 13 | bitstream 14 | incremental_db 15 | *.hdb 16 | *.cdb 17 | *.ddb 18 | *.idb 19 | *.rdb 20 | *.logdb 21 | *.qmsg 22 | *.hsd 23 | *.ammdb 24 | db 25 | *.rpt 26 | *.sld 27 | *.jdi 28 | *.done 29 | *.pow.* 30 | Makefile 31 | project 32 | project.tcl 33 | files.tcl 34 | *.vcd 35 | *.vvp 36 | run.command 37 | modelsim.ini 38 | transcript 39 | work/ 40 | *.wlf 41 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Sameer Puri 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sameer Puri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Manifest.py: -------------------------------------------------------------------------------- 1 | modules = { 2 | "local": "./src/" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hdmi 2 | 3 | [English](./README.md) | [Français](./README_fr.md) | [Help translate](https://github.com/hdl-util/hdmi/issues/11) 4 | 5 | ![hdmi](https://github.com/hdl-util/hdmi/workflows/hdmi/badge.svg) 6 | 7 | SystemVerilog code for HDMI 1.4b video/audio output on an [FPGA](https://simple.wikipedia.org/wiki/Field-programmable_gate_array). 8 | 9 | ## Why? 10 | 11 | Most free and open source HDMI source (computer/gaming console) implementations actually output a DVI signal, which HDMI sinks (TVs/monitors) are backwards compatible with. To support audio and other HDMI-only functionality, a true HDMI signal must be sent. The code in this repository lets you do that without having to license an HDMI IP block from anyone. 12 | 13 | ### Demo: VGA-compatible text mode, 720x480p on a Dell Ultrasharp 1080p Monitor 14 | 15 | ![GIF showing VGA-compatible text mode on a monitor](demo.gif) 16 | 17 | ## License 18 | 19 | This project is dual-licensed under MIT and Apache 2.0. 20 | 21 | `SPDX-License-Identifier: MIT OR Apache-2.0` 22 | 23 | ## Usage 24 | 25 | 1. Take files from `src/` and add them to your own project. If you use [hdlmake](https://hdlmake.readthedocs.io/en/master/), you can add this repository itself as a remote module. 26 | 1. Other helpful modules for displaying text / generating sound are also available in this GitHub organization. 27 | 1. Consult the simple usage example in `top/top.sv`. 28 | 1. See [hdmi-demo](https://github.com/hdl-util/hdmi-demo) for code that runs the demo as seen the demo GIF. 29 | 1. Read through the parameters in `hdmi.sv` and tailor any instantiations to your situation. 30 | 1. Please create an issue if you run into a problem or have any questions. Make sure you have consulted the troubleshooting section first. 31 | 32 | ### Platform Support 33 | 34 | - [x] Altera (tested on [MKR Vidor 4000](https://store.arduino.cc/usa/mkr-vidor-4000)) 35 | - [x] Xilinx (tested on [Spartan Edge Accelerator Board](https://www.seeedstudio.com/Spartan-Edge-Accelerator-Board-p-4261.html)) 36 | - [ ] Lattice (unknown) 37 | - [ ] Gowin (WIP, active testing on Tang Nano 9K) 38 | 39 | ### To-do List (upon request) 40 | - [x] 24-bit color 41 | - [x] Data island packets 42 | - [x] Null packet 43 | - [x] ECC with BCH systematic encoding GF(2^8) 44 | - [x] Audio clock regeneration 45 | - [x] L-PCM audio 46 | - [x] 2-channel 47 | - [ ] 3-channel to 8-channel 48 | - [ ] 1-bit audio 49 | - [x] Audio InfoFrame 50 | - [x] Auxiliary Video Information InfoFrame 51 | - [x] Source Product Descriptor InfoFrame 52 | - [ ] MPEG Source InfoFrame 53 | - NOTE—Problems with the MPEG Source Infoframe have been identified that were not able to be fixed in time for CEA-861-D. Implementation is strongly discouraged until a future revision fixes the problems 54 | - [ ] Gamut Metadata 55 | - [x] Video formats 1, 2, 3, 4, 16, 17, 18, and 19 56 | - [x] VGA-compatible text mode 57 | - [x] IBM 8x16 font 58 | - [ ] Alternate fonts 59 | - [ ] Other color formats (YCbCr, deep color, etc.) 60 | - [ ] Support other video id codes 61 | - [ ] Interlaced video 62 | - [ ] Pixel repetition 63 | 64 | 65 | ### Pixel Clock 66 | 67 | You'll need to set up a PLL for producing the two HDMI clocks. The pixel clock for each supported format is shown below: 68 | 69 | |Video Resolution|Video ID Code(s)|Refresh Rate|Pixel Clock Frequency|[Progressive](https://en.wikipedia.org/wiki/Progressive_scan)/[Interlaced](https://en.wikipedia.org/wiki/Interlaced_video)| 70 | |---|---|---|---|---| 71 | |640x480|1|60Hz|25.2MHz|P| 72 | |640x480|1|59.94Hz|25.175MHz|P| 73 | |720x480|2, 3|60Hz|27.027MHz|P| 74 | |720x480|2, 3|59.94Hz|27MHz|P| 75 | |720x576|17, 18|50Hz|27MHz|P| 76 | |1280x720|4|60Hz|74.25MHz|P| 77 | |1280x720|4|59.94Hz|74.176MHz|P| 78 | |1280x720|19|50Hz|74.25MHz|P| 79 | |1920x1080|16|60Hz|148.5MHz|P| 80 | |1920x1080|16|59.94Hz|148.352MHz|P| 81 | |1920x1080|34|30Hz|74.25MHz|P| 82 | |1920x1080|34|29.97Hz|74.176MHz|P| 83 | |3840x2160 (not ready)|97, 107|60Hz|594MHz|P| 84 | |3840x2160|95, 105|30Hz|297MHz|P| 85 | 86 | The second clock is a clock 5 times as fast as the pixel clock. Even if your FPGA only has a single PLL, the Altera MegaWizard (or the Xilinx equivalent) should still be able to produce both. See [hdl-util/hdmi-demo](https://github.com/hdl-util/hdmi-demo/) for example PLLs. 87 | 88 | ### L-PCM Audio Bitrate / Sampling Frequency 89 | 90 | Both audio bitrate and frequency are specified as parameters of the HDMI module. Bitrate can be any value from 16 through 24. Below is a simple mapping of sample frequency to the appropriate parameter 91 | 92 | **WARNING: the audio can be REALLY LOUD if you use the full dynamic range with hand-generated waveforms! Using less dynamic range means you won't be deafened! (i.e. audio_sample >> 8 )** 93 | 94 | |Sampling Frequency|AUDIO_RATE value| 95 | |---|---| 96 | |32 kHz|32000| 97 | |44.1 kHz|44100| 98 | |88.2 kHz|88200| 99 | |176.4 kHz|176400| 100 | |48 kHz|48000| 101 | |96 kHz|96000| 102 | |192 kHz|192000| 103 | 104 | 105 | ### Source Device Information Code 106 | 107 | This code is sent in the Source Product Description InfoFrame via `SOURCE_DEVICE_INFORMATION` to give HDMI sinks an idea of what capabilities an HDMI source might have. It may be used for displaying a relevant icon in an input list (i.e. DVD logo for a DVD player). 108 | 109 | |Code|Source Device Information| 110 | |---|---| 111 | |0x00|Unknown| 112 | |0x01|Digital Set-top Box| 113 | |0x02|DVD Player| 114 | |0x03|Digital VHS| 115 | |0x04|HDD Videorecorder| 116 | |0x05|Digital Video Camera| 117 | |0x06|Digital Still Camera| 118 | |0x07|Video CD| 119 | |0x08|Game| 120 | |0x09|PC General| 121 | |0x0a|Blu-Ray Disc| 122 | |0x0b|Super Audio CD| 123 | |0x0c|HD DVD| 124 | |0x0d|Portable Media Player| 125 | 126 | ### Things to be aware of / Troubleshooting 127 | 128 | * Limited resolution: some FPGAs don't support I/O at speeds high enough to achieve 720p/1080p 129 | * Workaround: Altera FPGA users can try to specify speed grade C6 and see if it works, though yours may be C7 or C8. Beware that this might introduce some system instability. 130 | * FPGA does not support TMDS: many FPGAs without a dedicated HDMI output don't support TMDS 131 | * You should be able to directly use LVDS (3.3v) instead, tested up to 720x480 132 | * This might not work if your video has a high number of transitions or you plan to use higher resolutions 133 | * Solution: AC-couple the 3.3v LVDS wires to by adding 100nF capacitors in series, as close to the transmitter as possible 134 | * Why? TMDS is current mode logic, and driving a CML receiver with LVDS is detailed in [Figure 9 of Interfacing LVDS with other differential-I/O types](https://web.archive.org/web/20151123084833/https://m.eet.com/media/1135468/330072.pdf) 135 | * Resistors are not needed since Vcc = 3.3v for both the transmitter and receiver 136 | * Example: See `J13`, on the [Arduino MKR Vivado 4000 schematic](https://content.arduino.cc/assets/vidor_c10_sch.zip), where LVDS IO Standard pins on a Cyclone 10 FPGA have 100nF series capacitors 137 | * Poor wiring: if you're using a breakout board or long lengths of untwisted wire, there might be a few pixels that jitter due to interference 138 | * Make sure you have all the necessary pins connected (GND pins, etc.) 139 | * Try switching your HDMI cable; some cheap cables like [these I got from Amazon](https://www.amazon.com/gp/product/B01JO9PB7E/) have poor shielding 140 | * Hot-Plug unaware: all modules are unaware of hotplug 141 | * This shouldn't affect anything in the long term; the only stateful value is `hdmi.tmds_channel[2:0].acc` 142 | * You should decide hotplug behavior (i.e. pause/resume on disconnect/connect, or ignore it) 143 | * EDID not implemented: it is assumed you know what format you want at synthesis time, so there is no dynamic decision on video format 144 | * To be implemented in a display protocol independent manner 145 | * SCL/SCA voltage level: though unused by this implementation...it is I2C on a 5V logic level, as confirmed in the [TPD12S016 datasheet](https://www.ti.com/lit/ds/symlink/tpd12s016.pdf), which is unsupported by most FPGAs 146 | * Solution: use a bidirectional logic level shifter compatible with I2C to convert 3.3v LVTTL to 5v 147 | * Solution: use 3.3-V LVTTL I/O standard with 6.65k pull-up resistors to 3.3v (as done in `J13` on the [Arduino MKR Vivado 4000 schematic](https://content.arduino.cc/assets/vidor_c10_sch.zip)) 148 | * Emailed Arduino support: safe to use as long as the HDMI slave does not have pull-ups 149 | 150 | ## Licensing 151 | 152 | Dual-licensed under Apache License 2.0 and MIT License. 153 | 154 | ### HDMI Adoption 155 | 156 | I am NOT a lawyer, the below advice is given based on discussion from [a Hacker News post](https://news.ycombinator.com/item?id=22279308) and my research. 157 | 158 | HDMI itself is not a royalty free technology, unfortunately. You are free to use it for testing, development, etc. but to receive the HDMI LA's (licensing administration) blessing to create and sell end-user products: 159 | 160 | 161 | > The manufacturer of the finished end-user product MUST be a licensed HDMI Adopter, and 162 | > The finished end-user product MUST satisfy all requirements as defined in the Adopter Agreement including but not limited to passing compliance testing either at an HDMI ATC or through self-testing. 163 | 164 | 165 | Becoming an adopter means you have to pay a flat annual fee (~ $1k-$2k) and a per device royalty (~ $0.05). If you are selling an end-user device and DO NOT want to become an adopter, you can turn on the `DVI_OUTPUT` parameter, which will disable any HDMI-only logic, like audio. 166 | 167 | Please consult your lawyer if you have any concerns. Here are a few noteworthy cases that may help you make a decision: 168 | 169 | * Arduino LLC is not an adopter, yet sells the [Arduino MKR Vidor 4000](https://store.arduino.cc/usa/mkr-vidor-4000) FPGA 170 | * It has a micro-HDMI connector 171 | * [Having an HDMI connector does not require a license](https://electronics.stackexchange.com/questions/28202/legality-of-using-hdmi-connectors-in-non-hdmi-product) 172 | * Official examples provided by Arduino on GitHub only perform DVI output 173 | * It is a user's choice to program the FPGA for HDMI output 174 | * Therefore: the device isn't an end-user product under the purview of HDMI LA 175 | * Unlicensed DisplayPort to HDMI cables (2011) 176 | * [Articles suggests that the HDMI LA can recall illegal products](https://www.pcmag.com/archive/displayport-to-hdmi-cables-illegal-could-be-recalled-266671?amp=1). 177 | * But these cables [are still sold on Amazon](https://www.amazon.com/s?k=hdmi+to+displayport+cable) 178 | * Therefore: the power of HDMI LA to enforce licensing is unclear 179 | * [Terminated Adopters](https://hdmi.org/adopter/terminated) 180 | * There are currently 1,043 terminated adopters 181 | * Includes noteworthy companies like Xilinx, Lattice Semiconductor, Cypress Semiconductor, EVGA (!), etc. 182 | * No conclusion 183 | * Raspberry Pi Trading Ltd is licensed 184 | * They include the HDMI logo for products 185 | * Therefore: Raspberry Pi products are legal, licensed end-user products 186 | 187 | ## Alternative Implementations 188 | 189 | - [HDMI Intel FPGA IP Core](https://www.intel.com/content/www/us/en/programmable/products/intellectual-property/ip/interface-protocols/m-alt-hdmi-megacore.html): Stratix/Arria/Cyclone 190 | - [Xilinx HDMI solutions](https://www.xilinx.com/products/intellectual-property/hdmi.html#overview): Virtex/Kintex/Zynq/Artix 191 | - [Artix 7 HDMI Processing](https://github.com/hamsternz/Artix-7-HDMI-processing): VHDL, decode & encode 192 | - [SimpleVOut](https://github.com/cliffordwolf/SimpleVOut): many formats, no auxiliary data 193 | 194 | If you know of another good alternative, open an issue and it will be added. 195 | 196 | ## Reference Documents 197 | 198 | *These documents are not hosted here! They are available on [Library Genesis](https://libgen.is/) and at other locations.* 199 | 200 | * [HDMI Specification v1.4b](https://b-ok.cc/book/5499564/fe35f4) (dead link but also on libgen) 201 | * [HDMI Specification v2.0](https://b-ok.cc/book/5464885/1f0b4c) (dead link but also on libgen) 202 | * [EIA-CEA861-D.pdf](https://libgen.is/book/index.php?md5=CEE424CA0F098096B6B4EC32C32F80AA) 203 | * [CTA-861-G.pdf](https://b-ok.cc/book/5463292/52859e) (dead link but also on libgen) 204 | * [DVI Specification v1.0](https://www.cs.unc.edu/~stc/FAQs/Video/dvi_spec-V1_0.pdf) 205 | * [IEC 60958-1](https://ia803003.us.archive.org/30/items/gov.in.is.iec.60958.1.2004/is.iec.60958.1.2004.pdf) 206 | * [IEC 60958-3](https://ia800905.us.archive.org/22/items/gov.in.is.iec.60958.3.2003/is.iec.60958.3.2003.pdf) 207 | * [E-DDC v1.2](https://glenwing.github.io/docs/) 208 | 209 | ## Special Thanks 210 | 211 | * Mike Field's (@hamsternz) demos of DVI and HDMI output for helping me better understand HDMI 212 | * http://www.hamsterworks.co.nz/mediawiki/index.php/Dvid_test 213 | * http://www.hamsterworks.co.nz/mediawiki/index.php/Minimal_DVI-D 214 | * Jean P. Nicolle (fpga4fun.com) for sparking my interest in HDMI 215 | * https://www.fpga4fun.com/HDMI.html 216 | * Bureau of Indian Standards for free equivalents of non-free IEC standards 60958-1, 60958-3, etc. 217 | * @glenwing for [links to many VESA standard documents](https://glenwing.github.io/docs/) 218 | -------------------------------------------------------------------------------- /README_fr.md: -------------------------------------------------------------------------------- 1 | # hdmi 2 | 3 | [English](./README.md) | [Français](./README_fr.md) | [Nous aider avec la traduction](https://github.com/hdl-util/hdmi/issues/11) 4 | 5 | ![hdmi](https://github.com/hdl-util/hdmi/workflows/hdmi/badge.svg) 6 | 7 | SystemVerilog code pour transmettre vidéo/audio HDMI 1.4a sur un [FPGA](https://fr.wikipedia.org/wiki/Circuit_logique_programmable#FPGA). 8 | 9 | ## Pourquoi? 10 | 11 | La plupart des implementations open source d'un source HDMI transmettre en réalité un signal DVI, avec qui les sinks HDMI (i.e. TVs/moniteurs) sont rétrocompatible. Pour supporter audio et l'autre HDMI seulement fonctionnalité, on doit transmettre un signal HDMI vrai. Le code dans ce dépôt permettez-vous de faire ça sans licencer un bloc HDMI IP de n'importe qui. 12 | 13 | ### Démo: Mode texte VGA-compatible, 720x480p sur un Moniteur Dell Ultrasharp 1080p 14 | 15 | ![GIF qui montre mode texte VGA-compatible sur un moniteur](demo.gif) 16 | 17 | ## Usage 18 | 19 | 1. Take files from `src/` and add them to your own project. If you use [hdlmake](https://hdlmake.readthedocs.io/en/master/), you can add this repository itself as a remote module. 20 | 1. Other helpful modules for displaying text / generating sound are also available in this GitHub organization. 21 | 1. Consult the simple usage example in `top/top.sv`. 22 | 1. See [hdmi-demo](https://github.com/hdl-util/hdmi-demo) for code that runs the demo as seen the demo GIF. 23 | 1. Read through the parameters in `hdmi.sv` and tailor any instantiations to your situation. 24 | 1. Please create an issue if you run into a problem or have any questions. Make sure you have consulted the troubleshooting section first. 25 | 26 | 27 | S'il vous plaît regarder le readme anglais pour l'information à jour. 28 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdl-util/hdmi/fbade3d11a58b885a6084ec75eae25339623355d/demo.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hdlmake==3.3 2 | six==1.14.0 3 | -------------------------------------------------------------------------------- /sim/audio_clock_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "audio_clock_tb" 4 | 5 | sim_post_cmd = "vsim -do ../vsim.do -c audio_clock_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/audio_clock_tb" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/audio_param_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "audio_param_tb" 4 | 5 | sim_post_cmd = "vsim -do ../vsim.do -c audio_param_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/audio_param_tb" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/spd_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "spd_tb" 4 | 5 | sim_post_cmd = "vsim -do ../vsim.do -c spd_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/spd_tb" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/top_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "top_tb" 4 | 5 | sim_post_cmd = "vsim -do ../vsim.do -c top_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/top_tb" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/vsim.do: -------------------------------------------------------------------------------- 1 | onfinish stop 2 | run -all 3 | if { [runStatus -full] == "break simulation_stop {\$finish}" } { 4 | echo Build succeeded 5 | quit -f -code 0 6 | } else { 7 | echo Build failed with status [runStatus -full] 8 | quit -f -code 1 9 | } 10 | -------------------------------------------------------------------------------- /src/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "hdmi.sv", 3 | "tmds_channel.sv", 4 | "packet_assembler.sv", 5 | "packet_picker.sv", 6 | "serializer.sv", 7 | "auxiliary_video_information_info_frame.sv", 8 | "source_product_description_info_frame.sv", 9 | "audio_clock_regeneration_packet.sv", 10 | "audio_info_frame.sv", 11 | "audio_sample_packet.sv" 12 | ] 13 | -------------------------------------------------------------------------------- /src/audio_clock_regeneration_packet.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI audio clock regeneration packet 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | // See HDMI 1.4b Section 5.3.3 5 | module audio_clock_regeneration_packet 6 | #( 7 | parameter real VIDEO_RATE = 25.2E6, 8 | parameter int AUDIO_RATE = 48e3 9 | ) 10 | ( 11 | input logic clk_pixel, 12 | input logic clk_audio, 13 | output logic clk_audio_counter_wrap = 0, 14 | output logic [23:0] header, 15 | output logic [55:0] sub [3:0] 16 | ); 17 | 18 | // See Section 7.2.3, values derived from "Other" row in Tables 7-1, 7-2, 7-3. 19 | localparam bit [19:0] N = AUDIO_RATE % 125 == 0 ? 20'(16 * AUDIO_RATE / 125) : AUDIO_RATE % 225 == 0 ? 20'(32 * AUDIO_RATE / 225) : 20'(AUDIO_RATE * 16 / 125); 20 | 21 | localparam int CLK_AUDIO_COUNTER_WIDTH = $clog2(N / 128); 22 | localparam bit [CLK_AUDIO_COUNTER_WIDTH-1:0] CLK_AUDIO_COUNTER_END = CLK_AUDIO_COUNTER_WIDTH'(N / 128 - 1); 23 | logic [CLK_AUDIO_COUNTER_WIDTH-1:0] clk_audio_counter = CLK_AUDIO_COUNTER_WIDTH'(0); 24 | logic internal_clk_audio_counter_wrap = 1'd0; 25 | always_ff @(posedge clk_audio) 26 | begin 27 | if (clk_audio_counter == CLK_AUDIO_COUNTER_END) 28 | begin 29 | clk_audio_counter <= CLK_AUDIO_COUNTER_WIDTH'(0); 30 | internal_clk_audio_counter_wrap <= !internal_clk_audio_counter_wrap; 31 | end 32 | else 33 | clk_audio_counter <= clk_audio_counter + 1'd1; 34 | end 35 | 36 | logic [1:0] clk_audio_counter_wrap_synchronizer_chain = 2'd0; 37 | always_ff @(posedge clk_pixel) 38 | clk_audio_counter_wrap_synchronizer_chain <= {internal_clk_audio_counter_wrap, clk_audio_counter_wrap_synchronizer_chain[1]}; 39 | 40 | localparam bit [19:0] CYCLE_TIME_STAMP_COUNTER_IDEAL = 20'(int'(VIDEO_RATE * int'(N) / 128 / AUDIO_RATE)); 41 | localparam int CYCLE_TIME_STAMP_COUNTER_WIDTH = $clog2(20'(int'(real'(CYCLE_TIME_STAMP_COUNTER_IDEAL) * 1.1))); // Account for 10% deviation in audio clock 42 | 43 | logic [19:0] cycle_time_stamp = 20'd0; 44 | logic [CYCLE_TIME_STAMP_COUNTER_WIDTH-1:0] cycle_time_stamp_counter = CYCLE_TIME_STAMP_COUNTER_WIDTH'(0); 45 | always_ff @(posedge clk_pixel) 46 | begin 47 | if (clk_audio_counter_wrap_synchronizer_chain[1] ^ clk_audio_counter_wrap_synchronizer_chain[0]) 48 | begin 49 | cycle_time_stamp_counter <= CYCLE_TIME_STAMP_COUNTER_WIDTH'(0); 50 | cycle_time_stamp <= {(20-CYCLE_TIME_STAMP_COUNTER_WIDTH)'(0), cycle_time_stamp_counter + CYCLE_TIME_STAMP_COUNTER_WIDTH'(1)}; 51 | clk_audio_counter_wrap <= !clk_audio_counter_wrap; 52 | end 53 | else 54 | cycle_time_stamp_counter <= cycle_time_stamp_counter + CYCLE_TIME_STAMP_COUNTER_WIDTH'(1); 55 | end 56 | 57 | // "An HDMI Sink shall ignore bytes HB1 and HB2 of the Audio Clock Regeneration Packet header." 58 | `ifdef MODEL_TECH 59 | assign header = {8'd0, 8'd0, 8'd1}; 60 | `else 61 | assign header = {8'dX, 8'dX, 8'd1}; 62 | `endif 63 | 64 | // "The four Subpackets each contain the same Audio Clock regeneration Subpacket." 65 | genvar i; 66 | generate 67 | for (i = 0; i < 4; i++) 68 | begin: same_packet 69 | assign sub[i] = {N[7:0], N[15:8], {4'd0, N[19:16]}, cycle_time_stamp[7:0], cycle_time_stamp[15:8], {4'd0, cycle_time_stamp[19:16]}, 8'd0}; 70 | end 71 | endgenerate 72 | 73 | endmodule 74 | -------------------------------------------------------------------------------- /src/audio_info_frame.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI audio info frame 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | // See Section 8.2.2 5 | module audio_info_frame 6 | #( 7 | parameter bit [2:0] AUDIO_CHANNEL_COUNT = 3'd1, // 2 channels. See CEA-861-D table 17 for details. 8 | parameter bit [7:0] CHANNEL_ALLOCATION = 8'h00, // Channel 0 = Front Left, Channel 1 = Front Right (0-indexed) 9 | parameter bit DOWN_MIX_INHIBITED = 1'b0, // Permitted or no information about any assertion of this. The DM_INH field is to be set only for DVD-Audio applications. 10 | parameter bit [3:0] LEVEL_SHIFT_VALUE = 4'd0, // 4-bit unsigned number from 0dB up to 15dB, used for downmixing. 11 | parameter bit [1:0] LOW_FREQUENCY_EFFECTS_PLAYBACK_LEVEL = 2'b00 // No information, LFE = bass-only info < 120Hz, used in Dolby Surround. 12 | ) 13 | ( 14 | output logic [23:0] header, 15 | output logic [55:0] sub [3:0] 16 | ); 17 | 18 | // NOTE—HDMI requires the coding type, sample size and sample frequency fields to be set to 0 ("Refer to Stream Header") as these items are carried in the audio stream 19 | localparam bit [3:0] AUDIO_CODING_TYPE = 4'd0; // Refer to stream header. 20 | localparam bit [2:0] SAMPLING_FREQUENCY = 3'd0; // Refer to stream header. 21 | localparam bit [1:0] SAMPLE_SIZE = 2'd0; // Refer to stream header. 22 | 23 | localparam bit [4:0] LENGTH = 5'd10; 24 | localparam bit [7:0] VERSION = 8'd1; 25 | localparam bit [6:0] TYPE = 7'd4; 26 | 27 | assign header = {{3'b0, LENGTH}, VERSION, {1'b1, TYPE}}; 28 | 29 | // PB0-PB6 = sub0 30 | // PB7-13 = sub1 31 | // PB14-20 = sub2 32 | // PB21-27 = sub3 33 | logic [7:0] packet_bytes [27:0]; 34 | 35 | assign packet_bytes[0] = 8'd1 + ~(header[23:16] + header[15:8] + header[7:0] + packet_bytes[5] + packet_bytes[4] + packet_bytes[3] + packet_bytes[2] + packet_bytes[1]); 36 | assign packet_bytes[1] = {AUDIO_CODING_TYPE, 1'b0, AUDIO_CHANNEL_COUNT}; 37 | assign packet_bytes[2] = {3'd0, SAMPLING_FREQUENCY, SAMPLE_SIZE}; 38 | assign packet_bytes[3] = 8'd0; 39 | assign packet_bytes[4] = CHANNEL_ALLOCATION; 40 | assign packet_bytes[5] = {DOWN_MIX_INHIBITED, LEVEL_SHIFT_VALUE, 1'b0, LOW_FREQUENCY_EFFECTS_PLAYBACK_LEVEL}; 41 | 42 | genvar i; 43 | generate 44 | for (i = 6; i < 28; i++) 45 | begin: pb_reserved 46 | assign packet_bytes[i] = 8'd0; 47 | end 48 | for (i = 0; i < 4; i++) 49 | begin: pb_to_sub 50 | assign sub[i] = {packet_bytes[6 + i*7], packet_bytes[5 + i*7], packet_bytes[4 + i*7], packet_bytes[3 + i*7], packet_bytes[2 + i*7], packet_bytes[1 + i*7], packet_bytes[0 + i*7]}; 51 | end 52 | endgenerate 53 | endmodule 54 | -------------------------------------------------------------------------------- /src/audio_sample_packet.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI audio sample packet 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | // Unless otherwise specified, all "See X" references will refer to the HDMI v1.4a specification. 5 | 6 | // See Section 5.3.4 7 | // 2-channel L-PCM or IEC 61937 audio in IEC 60958 frames with consumer grade IEC 60958-3. 8 | module audio_sample_packet 9 | #( 10 | // A thorough explanation of the below parameters can be found in IEC 60958-3 5.2, 5.3. 11 | 12 | // 0 = Consumer, 1 = Professional 13 | parameter bit GRADE = 1'b0, 14 | 15 | // 0 = LPCM, 1 = IEC 61937 compressed 16 | parameter bit SAMPLE_WORD_TYPE = 1'b0, 17 | 18 | // 0 = asserted, 1 = not asserted 19 | parameter bit COPYRIGHT_NOT_ASSERTED = 1'b1, 20 | 21 | // 000 = no pre-emphasis, 001 = 50μs/15μs pre-emphasis 22 | parameter bit [2:0] PRE_EMPHASIS = 3'b000, 23 | 24 | // Only one valid value 25 | parameter bit [1:0] MODE = 2'b00, 26 | 27 | // Set to all 0s for general device. 28 | parameter bit [7:0] CATEGORY_CODE = 8'd0, 29 | 30 | // TODO: not really sure what this is... 31 | // 0 = "Do no take into account" 32 | parameter bit [3:0] SOURCE_NUMBER = 4'd0, 33 | 34 | // 0000 = 44.1 kHz 35 | parameter bit [3:0] SAMPLING_FREQUENCY = 4'b0000, 36 | 37 | // Normal accuracy: +/- 1000 * 10E-6 (00), High accuracy +/- 50 * 10E-6 (01) 38 | parameter bit [1:0] CLOCK_ACCURACY = 2'b00, 39 | 40 | // 3-bit representation of the number of bits to subtract (except 101 is actually subtract 0) with LSB first, followed by maxmium length of 20 bits (0) or 24 bits (1) 41 | parameter bit [3:0] WORD_LENGTH = 0, 42 | 43 | // Frequency prior to conversion in a consumer playback system. 0000 = not indicated. 44 | parameter bit [3:0] ORIGINAL_SAMPLING_FREQUENCY = 4'b0000, 45 | 46 | // 2-channel = 0, >= 3-channel = 1 47 | parameter bit LAYOUT = 1'b0 48 | 49 | ) 50 | ( 51 | input logic [7:0] frame_counter, 52 | // See IEC 60958-1 4.4 and Annex A. 0 indicates the signal is suitable for decoding to an analog audio signal. 53 | input logic [1:0] valid_bit [3:0], 54 | // See IEC 60958-3 Section 6. 0 indicates that no user data is being sent 55 | input logic [1:0] user_data_bit [3:0], 56 | input logic [23:0] audio_sample_word [3:0] [1:0], 57 | input logic [3:0] audio_sample_word_present, 58 | output logic [23:0] header, 59 | output logic [55:0] sub [3:0] 60 | ); 61 | 62 | // Left/right channel for stereo audio 63 | logic [3:0] CHANNEL_LEFT = 4'd1; 64 | logic [3:0] CHANNEL_RIGHT = 4'd2; 65 | 66 | localparam bit [7:0] CHANNEL_STATUS_LENGTH = 8'd192; 67 | // See IEC 60958-1 5.1, Table 2 68 | logic [192-1:0] channel_status_left; 69 | assign channel_status_left = {152'd0, ORIGINAL_SAMPLING_FREQUENCY, WORD_LENGTH, 2'b00, CLOCK_ACCURACY, SAMPLING_FREQUENCY, CHANNEL_LEFT, SOURCE_NUMBER, CATEGORY_CODE, MODE, PRE_EMPHASIS, COPYRIGHT_NOT_ASSERTED, SAMPLE_WORD_TYPE, GRADE}; 70 | logic [CHANNEL_STATUS_LENGTH-1:0] channel_status_right; 71 | assign channel_status_right = {152'd0, ORIGINAL_SAMPLING_FREQUENCY, WORD_LENGTH, 2'b00, CLOCK_ACCURACY, SAMPLING_FREQUENCY, CHANNEL_RIGHT, SOURCE_NUMBER, CATEGORY_CODE, MODE, PRE_EMPHASIS, COPYRIGHT_NOT_ASSERTED, SAMPLE_WORD_TYPE, GRADE}; 72 | 73 | 74 | // See HDMI 1.4a Table 5-12: Audio Sample Packet Header. 75 | assign header[19:12] = {4'b0000, {3'b000, LAYOUT}}; 76 | assign header[7:0] = 8'd2; 77 | logic [1:0] parity_bit [3:0]; 78 | logic [7:0] aligned_frame_counter [3:0]; 79 | genvar i; 80 | generate 81 | for (i = 0; i < 4; i++) 82 | begin: sample_based_assign 83 | always_comb 84 | begin 85 | if (8'(frame_counter + i) >= CHANNEL_STATUS_LENGTH) 86 | aligned_frame_counter[i] = 8'(frame_counter + i - CHANNEL_STATUS_LENGTH); 87 | else 88 | aligned_frame_counter[i] = 8'(frame_counter + i); 89 | end 90 | assign header[23 - (3-i)] = aligned_frame_counter[i] == 8'd0 && audio_sample_word_present[i]; 91 | assign header[11 - (3-i)] = audio_sample_word_present[i]; 92 | assign parity_bit[i][0] = ^{channel_status_left[aligned_frame_counter[i]], user_data_bit[i][0], valid_bit[i][0], audio_sample_word[i][0]}; 93 | assign parity_bit[i][1] = ^{channel_status_right[aligned_frame_counter[i]], user_data_bit[i][1], valid_bit[i][1], audio_sample_word[i][1]}; 94 | // See HDMI 1.4a Table 5-13: Audio Sample Subpacket. 95 | always_comb 96 | begin 97 | if (audio_sample_word_present[i]) 98 | sub[i] = {{parity_bit[i][1], channel_status_right[aligned_frame_counter[i]], user_data_bit[i][1], valid_bit[i][1], parity_bit[i][0], channel_status_left[aligned_frame_counter[i]], user_data_bit[i][0], valid_bit[i][0]}, audio_sample_word[i][1], audio_sample_word[i][0]}; 99 | else 100 | `ifdef MODEL_TECH 101 | sub[i] = 56'd0; 102 | `else 103 | sub[i] = 56'dx; 104 | `endif 105 | end 106 | end 107 | endgenerate 108 | 109 | endmodule 110 | -------------------------------------------------------------------------------- /src/auxiliary_video_information_info_frame.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI Auxiliary Video InfoFrame packet. 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | // See Section 8.2.1 5 | module auxiliary_video_information_info_frame 6 | #( 7 | parameter bit [1:0] VIDEO_FORMAT = 2'b00, // 00 = RGB, 01 = YCbCr 4:2:2, 10 = YCbCr 4:4:4 8 | parameter bit ACTIVE_FORMAT_INFO_PRESENT = 1'b0, // Not valid 9 | parameter bit [1:0] BAR_INFO = 2'b00, // Not valid 10 | parameter bit [1:0] SCAN_INFO = 2'b00, // No data 11 | parameter bit [1:0] COLORIMETRY = 2'b00, // No data 12 | parameter bit [1:0] PICTURE_ASPECT_RATIO = 2'b00, // No data, See CEA-CEB16 for more information about Active Format Description processing. 13 | parameter bit [3:0] ACTIVE_FORMAT_ASPECT_RATIO = 4'b1000, // Not valid unless ACTIVE_FORMAT_INFO_PRESENT = 1'b1, then Same as picture aspect ratio 14 | parameter bit IT_CONTENT = 1'b0, // The IT content bit indicates when picture content is composed according to common IT practice (i.e. without regard to Nyquist criterion) and is unsuitable for analog reconstruction or filtering. When the IT content bit is set to 1, downstream processors should pass pixel data unfiltered and without analog reconstruction. 15 | parameter bit [2:0] EXTENDED_COLORIMETRY = 3'b000, // Not valid unless COLORIMETRY = 2'b11. The extended colorimetry bits, EC2, EC1, and EC0, describe optional colorimetry encoding that may be applicable to some implementations and are always present, whether their information is valid or not (see CEA 861-D Section 7.5.5). 16 | parameter bit [1:0] RGB_QUANTIZATION_RANGE = 2'b00, // Default. Displays conforming to CEA-861-D accept both a limited quantization range of 220 levels (16 to 235) anda full range of 256 levels (0 to 255) when receiving video with RGB color space (see CEA 861-D Sections 5.1, Section 5.2, Section 5.3 and Section 5.4). By default, RGB pixel data values should be assumed to have the limited range when receiving a CE video format, and the full range when receiving an IT format. The quantization bits allow the source to override this default and to explicitly indicate the current RGB quantization range. 17 | parameter bit [1:0] NON_UNIFORM_PICTURE_SCALING = 2'b00, // None. The Nonuniform Picture Scaling bits shall be set if the source device scales the picture or has determined that scaling has been performed in a specific direction. 18 | parameter int VIDEO_ID_CODE = 4, // Same as the one from the HDMI module 19 | parameter bit [1:0] YCC_QUANTIZATION_RANGE = 2'b00, // 00 = Limited, 01 = Full 20 | parameter bit [1:0] CONTENT_TYPE = 2'b00, // No data, becomes Graphics if IT_CONTENT = 1'b1. 21 | parameter bit [3:0] PIXEL_REPETITION = 4'b0000 // None 22 | ) 23 | ( 24 | output logic [23:0] header, 25 | output logic [55:0] sub [3:0] 26 | ); 27 | 28 | 29 | localparam bit [4:0] LENGTH = 5'd13; 30 | localparam bit [7:0] VERSION = 8'd2; 31 | localparam bit [6:0] TYPE = 7'd2; 32 | 33 | assign header = {{3'b0, LENGTH}, VERSION, {1'b1, TYPE}}; 34 | 35 | // PB0-PB6 = sub0 36 | // PB7-13 = sub1 37 | // PB14-20 = sub2 38 | // PB21-27 = sub3 39 | logic [7:0] packet_bytes [27:0]; 40 | 41 | assign packet_bytes[0] = 8'd1 + ~(header[23:16] + header[15:8] + header[7:0] + packet_bytes[13] + packet_bytes[12] + packet_bytes[11] + packet_bytes[10] + packet_bytes[9] + packet_bytes[8] + packet_bytes[7] + packet_bytes[6] + packet_bytes[5] + packet_bytes[4] + packet_bytes[3] + packet_bytes[2] + packet_bytes[1]); 42 | assign packet_bytes[1] = {1'b0, VIDEO_FORMAT, ACTIVE_FORMAT_INFO_PRESENT, BAR_INFO, SCAN_INFO}; 43 | assign packet_bytes[2] = {COLORIMETRY, PICTURE_ASPECT_RATIO, ACTIVE_FORMAT_ASPECT_RATIO}; 44 | assign packet_bytes[3] = {IT_CONTENT, EXTENDED_COLORIMETRY, RGB_QUANTIZATION_RANGE, NON_UNIFORM_PICTURE_SCALING}; 45 | assign packet_bytes[4] = {1'b0, 7'(VIDEO_ID_CODE)}; 46 | assign packet_bytes[5] = {YCC_QUANTIZATION_RANGE, CONTENT_TYPE, PIXEL_REPETITION}; 47 | 48 | genvar i; 49 | generate 50 | if (BAR_INFO != 2'b00) // Assign values to bars if BAR_INFO says they are valid. 51 | begin 52 | assign packet_bytes[6] = 8'hff; 53 | assign packet_bytes[7] = 8'hff; 54 | assign packet_bytes[8] = 8'h00; 55 | assign packet_bytes[9] = 8'h00; 56 | assign packet_bytes[10] = 8'hff; 57 | assign packet_bytes[11] = 8'hff; 58 | assign packet_bytes[12] = 8'h00; 59 | assign packet_bytes[13] = 8'h00; 60 | end else begin 61 | assign packet_bytes[6] = 8'h00; 62 | assign packet_bytes[7] = 8'h00; 63 | assign packet_bytes[8] = 8'h00; 64 | assign packet_bytes[9] = 8'h00; 65 | assign packet_bytes[10] = 8'h00; 66 | assign packet_bytes[11] = 8'h00; 67 | assign packet_bytes[12] = 8'h00; 68 | assign packet_bytes[13] = 8'h00; 69 | end 70 | for (i = 14; i < 28; i++) 71 | begin: pb_reserved 72 | assign packet_bytes[i] = 8'd0; 73 | end 74 | for (i = 0; i < 4; i++) 75 | begin: pb_to_sub 76 | assign sub[i] = {packet_bytes[6 + i*7], packet_bytes[5 + i*7], packet_bytes[4 + i*7], packet_bytes[3 + i*7], packet_bytes[2 + i*7], packet_bytes[1 + i*7], packet_bytes[0 + i*7]}; 77 | end 78 | endgenerate 79 | 80 | endmodule 81 | -------------------------------------------------------------------------------- /src/hdmi.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI Spec v1.4a 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | module hdmi 5 | #( 6 | // Defaults to 640x480 which should be supported by almost if not all HDMI sinks. 7 | // See README.md or CEA-861-D for enumeration of video id codes. 8 | // Pixel repetition, interlaced scans and other special output modes are not implemented (yet). 9 | parameter int VIDEO_ID_CODE = 1, 10 | 11 | // The IT content bit indicates that image samples are generated in an ad-hoc 12 | // manner (e.g. directly from values in a framebuffer, as by a PC video 13 | // card) and therefore aren't suitable for filtering or analog 14 | // reconstruction. This is probably what you want if you treat pixels 15 | // as "squares". If you generate a properly bandlimited signal or obtain 16 | // one from elsewhere (e.g. a camera), this can be turned off. 17 | // 18 | // This flag also tends to cause receivers to treat RGB values as full 19 | // range (0-255). 20 | parameter bit IT_CONTENT = 1'b1, 21 | 22 | // Defaults to minimum bit lengths required to represent positions. 23 | // Modify these parameters if you have alternate desired bit lengths. 24 | parameter int BIT_WIDTH = VIDEO_ID_CODE < 4 ? 10 : VIDEO_ID_CODE == 4 ? 11 : 12, 25 | parameter int BIT_HEIGHT = VIDEO_ID_CODE == 16 ? 11: 10, 26 | 27 | // A true HDMI signal sends auxiliary data (i.e. audio, preambles) which prevents it from being parsed by DVI signal sinks. 28 | // HDMI signal sinks are fortunately backwards-compatible with DVI signals. 29 | // Enable this flag if the output should be a DVI signal. You might want to do this to reduce resource usage or if you're only outputting video. 30 | parameter bit DVI_OUTPUT = 1'b0, 31 | 32 | // **All parameters below matter ONLY IF you plan on sending auxiliary data (DVI_OUTPUT == 1'b0)** 33 | 34 | // Specify the refresh rate in Hz you are using for audio calculations 35 | parameter real VIDEO_REFRESH_RATE = 59.94, 36 | 37 | // As specified in Section 7.3, the minimal audio requirements are met: 16-bit or more L-PCM audio at 32 kHz, 44.1 kHz, or 48 kHz. 38 | // See Table 7-4 or README.md for an enumeration of sampling frequencies supported by HDMI. 39 | // Note that sinks may not support rates above 48 kHz. 40 | parameter int AUDIO_RATE = 44100, 41 | 42 | // Defaults to 16-bit audio, the minmimum supported by HDMI sinks. Can be anywhere from 16-bit to 24-bit. 43 | parameter int AUDIO_BIT_WIDTH = 16, 44 | 45 | // Some HDMI sinks will show the source product description below to users (i.e. in a list of inputs instead of HDMI 1, HDMI 2, etc.). 46 | // If you care about this, change it below. 47 | parameter bit [8*8-1:0] VENDOR_NAME = {"Unknown", 8'd0}, // Must be 8 bytes null-padded 7-bit ASCII 48 | parameter bit [8*16-1:0] PRODUCT_DESCRIPTION = {"FPGA", 96'd0}, // Must be 16 bytes null-padded 7-bit ASCII 49 | parameter bit [7:0] SOURCE_DEVICE_INFORMATION = 8'h00, // See README.md or CTA-861-G for the list of valid codes 50 | 51 | // Starting screen coordinate when module comes out of reset. 52 | // 53 | // Setting these to something other than (0, 0) is useful when positioning 54 | // an external video signal within a larger overall frame (e.g. 55 | // letterboxing an input video signal). This allows you to synchronize the 56 | // negative edge of reset directly to the start of the external signal 57 | // instead of to some number of clock cycles before. 58 | // 59 | // You probably don't need to change these parameters if you are 60 | // generating a signal from scratch instead of processing an 61 | // external signal. 62 | parameter int START_X = 0, 63 | parameter int START_Y = 0 64 | ) 65 | ( 66 | input logic clk_pixel_x5, 67 | input logic clk_pixel, 68 | input logic clk_audio, 69 | // synchronous reset back to 0,0 70 | input logic reset, 71 | input logic [23:0] rgb, 72 | input logic [AUDIO_BIT_WIDTH-1:0] audio_sample_word [1:0], 73 | 74 | // These outputs go to your HDMI port 75 | output logic [2:0] tmds, 76 | output logic tmds_clock, 77 | 78 | // All outputs below this line stay inside the FPGA 79 | // They are used (by you) to pick the color each pixel should have 80 | // i.e. always_ff @(posedge pixel_clk) rgb <= {8'd0, 8'(cx), 8'(cy)}; 81 | output logic [BIT_WIDTH-1:0] cx = START_X, 82 | output logic [BIT_HEIGHT-1:0] cy = START_Y, 83 | 84 | // The screen is at the upper left corner of the frame. 85 | // 0,0 = 0,0 in video 86 | // the frame includes extra space for sending auxiliary data 87 | output logic [BIT_WIDTH-1:0] frame_width, 88 | output logic [BIT_HEIGHT-1:0] frame_height, 89 | output logic [BIT_WIDTH-1:0] screen_width, 90 | output logic [BIT_HEIGHT-1:0] screen_height 91 | ); 92 | 93 | localparam int NUM_CHANNELS = 3; 94 | logic hsync; 95 | logic vsync; 96 | 97 | logic [BIT_WIDTH-1:0] hsync_pulse_start, hsync_pulse_size; 98 | logic [BIT_HEIGHT-1:0] vsync_pulse_start, vsync_pulse_size; 99 | logic invert; 100 | 101 | // See CEA-861-D for more specifics formats described below. 102 | generate 103 | case (VIDEO_ID_CODE) 104 | 1: 105 | begin 106 | assign frame_width = 800; 107 | assign frame_height = 525; 108 | assign screen_width = 640; 109 | assign screen_height = 480; 110 | assign hsync_pulse_start = 16; 111 | assign hsync_pulse_size = 96; 112 | assign vsync_pulse_start = 10; 113 | assign vsync_pulse_size = 2; 114 | assign invert = 1; 115 | end 116 | 2, 3: 117 | begin 118 | assign frame_width = 858; 119 | assign frame_height = 525; 120 | assign screen_width = 720; 121 | assign screen_height = 480; 122 | assign hsync_pulse_start = 16; 123 | assign hsync_pulse_size = 62; 124 | assign vsync_pulse_start = 9; 125 | assign vsync_pulse_size = 6; 126 | assign invert = 1; 127 | end 128 | 4: 129 | begin 130 | assign frame_width = 1650; 131 | assign frame_height = 750; 132 | assign screen_width = 1280; 133 | assign screen_height = 720; 134 | assign hsync_pulse_start = 110; 135 | assign hsync_pulse_size = 40; 136 | assign vsync_pulse_start = 5; 137 | assign vsync_pulse_size = 5; 138 | assign invert = 0; 139 | end 140 | 16, 34: 141 | begin 142 | assign frame_width = 2200; 143 | assign frame_height = 1125; 144 | assign screen_width = 1920; 145 | assign screen_height = 1080; 146 | assign hsync_pulse_start = 88; 147 | assign hsync_pulse_size = 44; 148 | assign vsync_pulse_start = 4; 149 | assign vsync_pulse_size = 5; 150 | assign invert = 0; 151 | end 152 | 17, 18: 153 | begin 154 | assign frame_width = 864; 155 | assign frame_height = 625; 156 | assign screen_width = 720; 157 | assign screen_height = 576; 158 | assign hsync_pulse_start = 12; 159 | assign hsync_pulse_size = 64; 160 | assign vsync_pulse_start = 5; 161 | assign vsync_pulse_size = 5; 162 | assign invert = 1; 163 | end 164 | 19: 165 | begin 166 | assign frame_width = 1980; 167 | assign frame_height = 750; 168 | assign screen_width = 1280; 169 | assign screen_height = 720; 170 | assign hsync_pulse_start = 440; 171 | assign hsync_pulse_size = 40; 172 | assign vsync_pulse_start = 5; 173 | assign vsync_pulse_size = 5; 174 | assign invert = 0; 175 | end 176 | 95, 105, 97, 107: 177 | begin 178 | assign frame_width = 4400; 179 | assign frame_height = 2250; 180 | assign screen_width = 3840; 181 | assign screen_height = 2160; 182 | assign hsync_pulse_start = 176; 183 | assign hsync_pulse_size = 88; 184 | assign vsync_pulse_start = 8; 185 | assign vsync_pulse_size = 10; 186 | assign invert = 0; 187 | end 188 | endcase 189 | endgenerate 190 | 191 | always_comb begin 192 | hsync <= invert ^ (cx >= screen_width + hsync_pulse_start && cx < screen_width + hsync_pulse_start + hsync_pulse_size); 193 | // vsync pulses should begin and end at the start of hsync, so special 194 | // handling is required for the lines on which vsync starts and ends 195 | if (cy == screen_height + vsync_pulse_start - 1) 196 | vsync <= invert ^ (cx >= screen_width + hsync_pulse_start); 197 | else if (cy == screen_height + vsync_pulse_start + vsync_pulse_size - 1) 198 | vsync <= invert ^ (cx < screen_width + hsync_pulse_start); 199 | else 200 | vsync <= invert ^ (cy >= screen_height + vsync_pulse_start && cy < screen_height + vsync_pulse_start + vsync_pulse_size); 201 | end 202 | 203 | localparam real VIDEO_RATE = (VIDEO_ID_CODE == 1 ? 25.2E6 204 | : VIDEO_ID_CODE == 2 || VIDEO_ID_CODE == 3 ? 27.027E6 205 | : VIDEO_ID_CODE == 4 ? 74.25E6 206 | : VIDEO_ID_CODE == 16 ? 148.5E6 207 | : VIDEO_ID_CODE == 17 || VIDEO_ID_CODE == 18 ? 27E6 208 | : VIDEO_ID_CODE == 19 ? 74.25E6 209 | : VIDEO_ID_CODE == 34 ? 74.25E6 210 | : VIDEO_ID_CODE == 95 || VIDEO_ID_CODE == 105 || VIDEO_ID_CODE == 97 || VIDEO_ID_CODE == 107 ? 594E6 211 | : 0) * (VIDEO_REFRESH_RATE == 59.94 || VIDEO_REFRESH_RATE == 29.97 ? 1000.0/1001.0 : 1); // https://groups.google.com/forum/#!topic/sci.engr.advanced-tv/DQcGk5R_zsM 212 | 213 | // Wrap-around pixel position counters indicating the pixel to be generated by the user in THIS clock and sent out in the NEXT clock. 214 | always_ff @(posedge clk_pixel) 215 | begin 216 | if (reset) 217 | begin 218 | cx <= BIT_WIDTH'(START_X); 219 | cy <= BIT_HEIGHT'(START_Y); 220 | end 221 | else 222 | begin 223 | cx <= cx == frame_width-1'b1 ? BIT_WIDTH'(0) : cx + 1'b1; 224 | cy <= cx == frame_width-1'b1 ? cy == frame_height-1'b1 ? BIT_HEIGHT'(0) : cy + 1'b1 : cy; 225 | end 226 | end 227 | 228 | // See Section 5.2 229 | logic video_data_period = 0; 230 | always_ff @(posedge clk_pixel) 231 | begin 232 | if (reset) 233 | video_data_period <= 0; 234 | else 235 | video_data_period <= cx < screen_width && cy < screen_height; 236 | end 237 | 238 | logic [2:0] mode = 3'd1; 239 | logic [23:0] video_data = 24'd0; 240 | logic [5:0] control_data = 6'd0; 241 | logic [11:0] data_island_data = 12'd0; 242 | 243 | generate 244 | if (!DVI_OUTPUT) 245 | begin: true_hdmi_output 246 | logic video_guard = 1; 247 | logic video_preamble = 0; 248 | always_ff @(posedge clk_pixel) 249 | begin 250 | if (reset) 251 | begin 252 | video_guard <= 1; 253 | video_preamble <= 0; 254 | end 255 | else 256 | begin 257 | video_guard <= cx >= frame_width - 2 && cx < frame_width && (cy == frame_height - 1 || cy < screen_height - 1 /* no VG at end of last line */); 258 | video_preamble <= cx >= frame_width - 10 && cx < frame_width - 2 && (cy == frame_height - 1 || cy < screen_height - 1 /* no VP at end of last line */); 259 | end 260 | end 261 | 262 | // See Section 5.2.3.1 263 | int max_num_packets_alongside; 264 | logic [4:0] num_packets_alongside; 265 | always_comb 266 | begin 267 | max_num_packets_alongside = (frame_width - screen_width /* VD period */ - 2 /* V guard */ - 8 /* V preamble */ - 4 /* Min V control period */ - 2 /* DI trailing guard */ - 2 /* DI leading guard */ - 8 /* DI premable */ - 4 /* Min DI control period */) / 32; 268 | if (max_num_packets_alongside > 18) 269 | num_packets_alongside = 5'd18; 270 | else 271 | num_packets_alongside = 5'(max_num_packets_alongside); 272 | end 273 | 274 | logic data_island_period_instantaneous; 275 | assign data_island_period_instantaneous = num_packets_alongside > 0 && cx >= screen_width + 14 && cx < screen_width + 14 + num_packets_alongside * 32; 276 | logic packet_enable; 277 | assign packet_enable = data_island_period_instantaneous && 5'(cx + screen_width + 18) == 5'd0; 278 | 279 | logic data_island_guard = 0; 280 | logic data_island_preamble = 0; 281 | logic data_island_period = 0; 282 | always_ff @(posedge clk_pixel) 283 | begin 284 | if (reset) 285 | begin 286 | data_island_guard <= 0; 287 | data_island_preamble <= 0; 288 | data_island_period <= 0; 289 | end 290 | else 291 | begin 292 | data_island_guard <= num_packets_alongside > 0 && ( 293 | (cx >= screen_width + 12 && cx < screen_width + 14) /* leading guard */ || 294 | (cx >= screen_width + 14 + num_packets_alongside * 32 && cx < screen_width + 14 + num_packets_alongside * 32 + 2) /* trailing guard */ 295 | ); 296 | data_island_preamble <= num_packets_alongside > 0 && cx >= screen_width + 4 && cx < screen_width + 12; 297 | data_island_period <= data_island_period_instantaneous; 298 | end 299 | end 300 | 301 | // See Section 5.2.3.4 302 | logic [23:0] header; 303 | logic [55:0] sub [3:0]; 304 | logic video_field_end; 305 | assign video_field_end = cx == screen_width - 1'b1 && cy == screen_height - 1'b1; 306 | logic [4:0] packet_pixel_counter; 307 | packet_picker #( 308 | .VIDEO_ID_CODE(VIDEO_ID_CODE), 309 | .VIDEO_RATE(VIDEO_RATE), 310 | .IT_CONTENT(IT_CONTENT), 311 | .AUDIO_RATE(AUDIO_RATE), 312 | .AUDIO_BIT_WIDTH(AUDIO_BIT_WIDTH), 313 | .VENDOR_NAME(VENDOR_NAME), 314 | .PRODUCT_DESCRIPTION(PRODUCT_DESCRIPTION), 315 | .SOURCE_DEVICE_INFORMATION(SOURCE_DEVICE_INFORMATION) 316 | ) packet_picker (.clk_pixel(clk_pixel), .clk_audio(clk_audio), .reset(reset), .video_field_end(video_field_end), .packet_enable(packet_enable), .packet_pixel_counter(packet_pixel_counter), .audio_sample_word(audio_sample_word), .header(header), .sub(sub)); 317 | logic [8:0] packet_data; 318 | packet_assembler packet_assembler (.clk_pixel(clk_pixel), .reset(reset), .data_island_period(data_island_period), .header(header), .sub(sub), .packet_data(packet_data), .counter(packet_pixel_counter)); 319 | 320 | 321 | always_ff @(posedge clk_pixel) 322 | begin 323 | if (reset) 324 | begin 325 | mode <= 3'd2; 326 | video_data <= 24'd0; 327 | control_data = 6'd0; 328 | data_island_data <= 12'd0; 329 | end 330 | else 331 | begin 332 | mode <= data_island_guard ? 3'd4 : data_island_period ? 3'd3 : video_guard ? 3'd2 : video_data_period ? 3'd1 : 3'd0; 333 | video_data <= rgb; 334 | control_data <= {{1'b0, data_island_preamble}, {1'b0, video_preamble || data_island_preamble}, {vsync, hsync}}; // ctrl3, ctrl2, ctrl1, ctrl0, vsync, hsync 335 | data_island_data[11:4] <= packet_data[8:1]; 336 | data_island_data[3] <= cx != 0; 337 | data_island_data[2] <= packet_data[0]; 338 | data_island_data[1:0] <= {vsync, hsync}; 339 | end 340 | end 341 | end 342 | else // DVI_OUTPUT = 1 343 | begin 344 | always_ff @(posedge clk_pixel) 345 | begin 346 | if (reset) 347 | begin 348 | mode <= 3'd0; 349 | video_data <= 24'd0; 350 | control_data <= 6'd0; 351 | end 352 | else 353 | begin 354 | mode <= video_data_period ? 3'd1 : 3'd0; 355 | video_data <= rgb; 356 | control_data <= {4'b0000, {vsync, hsync}}; // ctrl3, ctrl2, ctrl1, ctrl0, vsync, hsync 357 | end 358 | end 359 | end 360 | endgenerate 361 | 362 | // All logic below relates to the production and output of the 10-bit TMDS code. 363 | logic [9:0] tmds_internal [NUM_CHANNELS-1:0] /* verilator public_flat */ ; 364 | genvar i; 365 | generate 366 | // TMDS code production. 367 | for (i = 0; i < NUM_CHANNELS; i++) 368 | begin: tmds_gen 369 | tmds_channel #(.CN(i)) tmds_channel (.clk_pixel(clk_pixel), .video_data(video_data[i*8+7:i*8]), .data_island_data(data_island_data[i*4+3:i*4]), .control_data(control_data[i*2+1:i*2]), .mode(mode), .tmds(tmds_internal[i])); 370 | end 371 | endgenerate 372 | 373 | serializer #(.NUM_CHANNELS(NUM_CHANNELS), .VIDEO_RATE(VIDEO_RATE)) serializer(.clk_pixel(clk_pixel), .clk_pixel_x5(clk_pixel_x5), .reset(reset), .tmds_internal(tmds_internal), .tmds(tmds), .tmds_clock(tmds_clock)); 374 | 375 | endmodule 376 | -------------------------------------------------------------------------------- /src/packet_assembler.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI packet ECC calculation. 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | module packet_assembler ( 5 | input logic clk_pixel, 6 | input logic reset, 7 | input logic data_island_period, 8 | input logic [23:0] header, // See Table 5-8 Packet Types 9 | input logic [55:0] sub [3:0], 10 | output logic [8:0] packet_data, // See Figure 5-4 Data Island Packet and ECC Structure 11 | output logic [4:0] counter = 5'd0 12 | ); 13 | 14 | // 32 pixel wrap-around counter. See Section 5.2.3.4 for further information. 15 | always_ff @(posedge clk_pixel) 16 | begin 17 | if (reset) 18 | counter <= 5'd0; 19 | else if (data_island_period) 20 | counter <= counter + 5'd1; 21 | end 22 | // BCH packets 0 to 3 are transferred two bits at a time, see Section 5.2.3.4 for further information. 23 | wire [5:0] counter_t2 = {counter, 1'b0}; 24 | wire [5:0] counter_t2_p1 = {counter, 1'b1}; 25 | 26 | // Initialize parity bits to 0 27 | logic [7:0] parity [4:0] = '{8'd0, 8'd0, 8'd0, 8'd0, 8'd0}; 28 | 29 | wire [63:0] bch [3:0]; 30 | assign bch[0] = {parity[0], sub[0]}; 31 | assign bch[1] = {parity[1], sub[1]}; 32 | assign bch[2] = {parity[2], sub[2]}; 33 | assign bch[3] = {parity[3], sub[3]}; 34 | wire [31:0] bch4 = {parity[4], header}; 35 | assign packet_data = {bch[3][counter_t2_p1], bch[2][counter_t2_p1], bch[1][counter_t2_p1], bch[0][counter_t2_p1], bch[3][counter_t2], bch[2][counter_t2], bch[1][counter_t2], bch[0][counter_t2], bch4[counter]}; 36 | 37 | // See Figure 5-5 Error Correction Code generator. Generalization of a CRC with binary BCH. 38 | // See https://web.archive.org/web/20190520020602/http://hamsterworks.co.nz/mediawiki/index.php/Minimal_HDMI#Computing_the_ECC for an explanation of the implementation. 39 | // See https://en.wikipedia.org/wiki/BCH_code#Systematic_encoding:_The_message_as_a_prefix for further information. 40 | function automatic [7:0] next_ecc; 41 | input [7:0] ecc, next_bch_bit; 42 | begin 43 | next_ecc = (ecc >> 1) ^ ((ecc[0] ^ next_bch_bit) ? 8'b10000011 : 8'd0); 44 | end 45 | endfunction 46 | 47 | logic [7:0] parity_next [4:0]; 48 | 49 | // The parity needs to be calculated 2 bits at a time for blocks 0 to 3. 50 | // There's 56 bits being sent 2 bits at a time over TMDS channels 1 & 2, so the parity bits wouldn't be ready in time otherwise. 51 | logic [7:0] parity_next_next [3:0]; 52 | 53 | genvar i; 54 | generate 55 | for(i = 0; i < 5; i++) 56 | begin: parity_calc 57 | if (i == 4) 58 | assign parity_next[i] = next_ecc(parity[i], header[counter]); 59 | else 60 | begin 61 | assign parity_next[i] = next_ecc(parity[i], sub[i][counter_t2]); 62 | assign parity_next_next[i] = next_ecc(parity_next[i], sub[i][counter_t2_p1]); 63 | end 64 | end 65 | endgenerate 66 | 67 | always_ff @(posedge clk_pixel) 68 | begin 69 | if (reset) 70 | parity <= '{8'd0, 8'd0, 8'd0, 8'd0, 8'd0}; 71 | else if (data_island_period) 72 | begin 73 | if (counter < 5'd28) // Compute ECC only on subpacket data, not on itself 74 | begin 75 | parity[3:0] <= parity_next_next; 76 | if (counter < 5'd24) // Header only has 24 bits, whereas subpackets have 56 and 56 / 2 = 28. 77 | parity[4] <= parity_next[4]; 78 | end 79 | else if (counter == 5'd31) 80 | parity <= '{8'd0, 8'd0, 8'd0, 8'd0, 8'd0}; // Reset ECC for next packet 81 | end 82 | else 83 | parity <= '{8'd0, 8'd0, 8'd0, 8'd0, 8'd0}; 84 | end 85 | 86 | endmodule 87 | -------------------------------------------------------------------------------- /src/packet_picker.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI packet choice logic. 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | module packet_picker 5 | #( 6 | parameter int VIDEO_ID_CODE = 4, 7 | parameter real VIDEO_RATE = 0, 8 | parameter bit IT_CONTENT = 1'b0, 9 | parameter int AUDIO_BIT_WIDTH = 0, 10 | parameter int AUDIO_RATE = 0, 11 | parameter bit [8*8-1:0] VENDOR_NAME = 0, 12 | parameter bit [8*16-1:0] PRODUCT_DESCRIPTION = 0, 13 | parameter bit [7:0] SOURCE_DEVICE_INFORMATION = 0 14 | ) 15 | ( 16 | input logic clk_pixel, 17 | input logic clk_audio, 18 | input logic reset, 19 | input logic video_field_end, 20 | input logic packet_enable, 21 | input logic [4:0] packet_pixel_counter, 22 | input logic [AUDIO_BIT_WIDTH-1:0] audio_sample_word [1:0], 23 | output logic [23:0] header, 24 | output logic [55:0] sub [3:0] 25 | ); 26 | 27 | // Connect the current packet type's data to the output. 28 | logic [7:0] packet_type = 8'd0; 29 | logic [23:0] headers [255:0]; 30 | logic [55:0] subs [255:0] [3:0]; 31 | assign header = headers[packet_type]; 32 | assign sub[0] = subs[packet_type][0]; 33 | assign sub[1] = subs[packet_type][1]; 34 | assign sub[2] = subs[packet_type][2]; 35 | assign sub[3] = subs[packet_type][3]; 36 | 37 | // NULL packet 38 | // "An HDMI Sink shall ignore bytes HB1 and HB2 of the Null Packet Header and all bytes of the Null Packet Body." 39 | `ifdef MODEL_TECH 40 | assign headers[0] = {8'd0, 8'd0, 8'd0}; assign subs[0] = '{56'd0, 56'd0, 56'd0, 56'd0}; 41 | `else 42 | assign headers[0] = {8'dX, 8'dX, 8'd0}; 43 | assign subs[0][0] = 56'dX; 44 | assign subs[0][1] = 56'dX; 45 | assign subs[0][2] = 56'dX; 46 | assign subs[0][3] = 56'dX; 47 | `endif 48 | 49 | // Audio Clock Regeneration Packet 50 | logic clk_audio_counter_wrap; 51 | audio_clock_regeneration_packet #(.VIDEO_RATE(VIDEO_RATE), .AUDIO_RATE(AUDIO_RATE)) audio_clock_regeneration_packet (.clk_pixel(clk_pixel), .clk_audio(clk_audio), .clk_audio_counter_wrap(clk_audio_counter_wrap), .header(headers[1]), .sub(subs[1])); 52 | 53 | // Audio Sample packet 54 | localparam bit [3:0] SAMPLING_FREQUENCY = AUDIO_RATE == 32000 ? 4'b0011 55 | : AUDIO_RATE == 44100 ? 4'b0000 56 | : AUDIO_RATE == 88200 ? 4'b1000 57 | : AUDIO_RATE == 176400 ? 4'b1100 58 | : AUDIO_RATE == 48000 ? 4'b0010 59 | : AUDIO_RATE == 96000 ? 4'b1010 60 | : AUDIO_RATE == 192000 ? 4'b1110 61 | : 4'bXXXX; 62 | localparam int AUDIO_BIT_WIDTH_COMPARATOR = AUDIO_BIT_WIDTH < 20 ? 20 : AUDIO_BIT_WIDTH == 20 ? 25 : AUDIO_BIT_WIDTH < 24 ? 24 : AUDIO_BIT_WIDTH == 24 ? 29 : -1; 63 | localparam bit [2:0] WORD_LENGTH = 3'(AUDIO_BIT_WIDTH_COMPARATOR - AUDIO_BIT_WIDTH); 64 | localparam bit WORD_LENGTH_LIMIT = AUDIO_BIT_WIDTH <= 20 ? 1'b0 : 1'b1; 65 | 66 | logic [AUDIO_BIT_WIDTH-1:0] audio_sample_word_transfer [1:0]; 67 | logic audio_sample_word_transfer_control = 1'd0; 68 | always_ff @(posedge clk_audio) 69 | begin 70 | audio_sample_word_transfer <= audio_sample_word; 71 | audio_sample_word_transfer_control <= !audio_sample_word_transfer_control; 72 | end 73 | 74 | logic [1:0] audio_sample_word_transfer_control_synchronizer_chain = 2'd0; 75 | always_ff @(posedge clk_pixel) 76 | audio_sample_word_transfer_control_synchronizer_chain <= {audio_sample_word_transfer_control, audio_sample_word_transfer_control_synchronizer_chain[1]}; 77 | 78 | logic sample_buffer_current = 1'b0; 79 | logic [1:0] samples_remaining = 2'd0; 80 | logic [23:0] audio_sample_word_buffer [1:0] [3:0] [1:0]; 81 | logic [AUDIO_BIT_WIDTH-1:0] audio_sample_word_transfer_mux [1:0]; 82 | always_comb 83 | begin 84 | if (audio_sample_word_transfer_control_synchronizer_chain[0] ^ audio_sample_word_transfer_control_synchronizer_chain[1]) 85 | audio_sample_word_transfer_mux = audio_sample_word_transfer; 86 | else 87 | audio_sample_word_transfer_mux = '{audio_sample_word_buffer[sample_buffer_current][samples_remaining][1][23:(24-AUDIO_BIT_WIDTH)], audio_sample_word_buffer[sample_buffer_current][samples_remaining][0][23:(24-AUDIO_BIT_WIDTH)]}; 88 | end 89 | 90 | logic sample_buffer_used = 1'b0; 91 | logic sample_buffer_ready = 1'b0; 92 | 93 | always_ff @(posedge clk_pixel) 94 | begin 95 | if (sample_buffer_used) 96 | sample_buffer_ready <= 1'b0; 97 | 98 | if (audio_sample_word_transfer_control_synchronizer_chain[0] ^ audio_sample_word_transfer_control_synchronizer_chain[1]) 99 | begin 100 | audio_sample_word_buffer[sample_buffer_current][samples_remaining][0] <= 24'(audio_sample_word_transfer_mux[0])<<(24-AUDIO_BIT_WIDTH); 101 | audio_sample_word_buffer[sample_buffer_current][samples_remaining][1] <= 24'(audio_sample_word_transfer_mux[1])<<(24-AUDIO_BIT_WIDTH); 102 | if (samples_remaining == 2'd3) 103 | begin 104 | samples_remaining <= 2'd0; 105 | sample_buffer_ready <= 1'b1; 106 | sample_buffer_current <= !sample_buffer_current; 107 | end 108 | else 109 | samples_remaining <= samples_remaining + 1'd1; 110 | end 111 | end 112 | 113 | logic [23:0] audio_sample_word_packet [3:0] [1:0]; 114 | logic [3:0] audio_sample_word_present_packet; 115 | 116 | logic [7:0] frame_counter = 8'd0; 117 | int k; 118 | always_ff @(posedge clk_pixel) 119 | begin 120 | if (reset) 121 | begin 122 | frame_counter <= 8'd0; 123 | end 124 | else if (packet_pixel_counter == 5'd31 && packet_type == 8'h02) // Keep track of current IEC 60958 frame 125 | begin 126 | frame_counter = frame_counter + 8'd4; 127 | if (frame_counter >= 8'd192) 128 | frame_counter = frame_counter - 8'd192; 129 | end 130 | end 131 | audio_sample_packet #(.SAMPLING_FREQUENCY(SAMPLING_FREQUENCY), .WORD_LENGTH({{WORD_LENGTH[0], WORD_LENGTH[1], WORD_LENGTH[2]}, WORD_LENGTH_LIMIT})) audio_sample_packet (.frame_counter(frame_counter), .valid_bit('{2'b00, 2'b00, 2'b00, 2'b00}), .user_data_bit('{2'b00, 2'b00, 2'b00, 2'b00}), .audio_sample_word(audio_sample_word_packet), .audio_sample_word_present(audio_sample_word_present_packet), .header(headers[2]), .sub(subs[2])); 132 | 133 | 134 | auxiliary_video_information_info_frame #( 135 | .VIDEO_ID_CODE(7'(VIDEO_ID_CODE)), 136 | .IT_CONTENT(IT_CONTENT) 137 | ) auxiliary_video_information_info_frame(.header(headers[130]), .sub(subs[130])); 138 | 139 | 140 | source_product_description_info_frame #(.VENDOR_NAME(VENDOR_NAME), .PRODUCT_DESCRIPTION(PRODUCT_DESCRIPTION), .SOURCE_DEVICE_INFORMATION(SOURCE_DEVICE_INFORMATION)) source_product_description_info_frame(.header(headers[131]), .sub(subs[131])); 141 | 142 | 143 | audio_info_frame audio_info_frame(.header(headers[132]), .sub(subs[132])); 144 | 145 | 146 | // "A Source shall always transmit... [an InfoFrame] at least once per two Video Fields" 147 | logic audio_info_frame_sent = 1'b0; 148 | logic auxiliary_video_information_info_frame_sent = 1'b0; 149 | logic source_product_description_info_frame_sent = 1'b0; 150 | logic last_clk_audio_counter_wrap = 1'b0; 151 | always_ff @(posedge clk_pixel) 152 | begin 153 | if (sample_buffer_used) 154 | sample_buffer_used <= 1'b0; 155 | 156 | if (reset || video_field_end) 157 | begin 158 | audio_info_frame_sent <= 1'b0; 159 | auxiliary_video_information_info_frame_sent <= 1'b0; 160 | source_product_description_info_frame_sent <= 1'b0; 161 | packet_type <= 8'dx; 162 | end 163 | else if (packet_enable) 164 | begin 165 | if (last_clk_audio_counter_wrap ^ clk_audio_counter_wrap) 166 | begin 167 | packet_type <= 8'd1; 168 | last_clk_audio_counter_wrap <= clk_audio_counter_wrap; 169 | end 170 | else if (sample_buffer_ready) 171 | begin 172 | packet_type <= 8'd2; 173 | audio_sample_word_packet <= audio_sample_word_buffer[!sample_buffer_current]; 174 | audio_sample_word_present_packet <= 4'b1111; 175 | sample_buffer_used <= 1'b1; 176 | end 177 | else if (!audio_info_frame_sent) 178 | begin 179 | packet_type <= 8'h84; 180 | audio_info_frame_sent <= 1'b1; 181 | end 182 | else if (!auxiliary_video_information_info_frame_sent) 183 | begin 184 | packet_type <= 8'h82; 185 | auxiliary_video_information_info_frame_sent <= 1'b1; 186 | end 187 | else if (!source_product_description_info_frame_sent) 188 | begin 189 | packet_type <= 8'h83; 190 | source_product_description_info_frame_sent <= 1'b1; 191 | end 192 | else 193 | packet_type <= 8'd0; 194 | end 195 | end 196 | 197 | endmodule 198 | -------------------------------------------------------------------------------- /src/serializer.sv: -------------------------------------------------------------------------------- 1 | module serializer 2 | #( 3 | parameter int NUM_CHANNELS = 3, 4 | parameter real VIDEO_RATE 5 | ) 6 | ( 7 | input logic clk_pixel, 8 | input logic clk_pixel_x5, 9 | input logic reset, 10 | input logic [9:0] tmds_internal [NUM_CHANNELS-1:0], 11 | output logic [2:0] tmds, 12 | output logic tmds_clock 13 | ); 14 | 15 | `ifndef VERILATOR 16 | `ifdef SYNTHESIS 17 | `ifndef ALTERA_RESERVED_QIS 18 | // https://www.xilinx.com/support/documentation/user_guides/ug471_7Series_SelectIO.pdf 19 | logic tmds_plus_clock [NUM_CHANNELS:0]; 20 | assign tmds_plus_clock = '{tmds_clock, tmds[2], tmds[1], tmds[0]}; 21 | logic [9:0] tmds_internal_plus_clock [NUM_CHANNELS:0]; 22 | assign tmds_internal_plus_clock = '{10'b0000011111, tmds_internal[2], tmds_internal[1], tmds_internal[0]}; 23 | logic [1:0] cascade [NUM_CHANNELS:0]; 24 | 25 | // this is requried for OSERDESE2 to work 26 | logic internal_reset = 1'b1; 27 | always @(posedge clk_pixel) 28 | begin 29 | internal_reset <= 1'b0; 30 | end 31 | genvar i; 32 | generate 33 | for (i = 0; i <= NUM_CHANNELS; i++) 34 | begin: xilinx_serialize 35 | OSERDESE2 #( 36 | .DATA_RATE_OQ("DDR"), 37 | .DATA_RATE_TQ("SDR"), 38 | .DATA_WIDTH(10), 39 | .SERDES_MODE("MASTER"), 40 | .TRISTATE_WIDTH(1), 41 | .TBYTE_CTL("FALSE"), 42 | .TBYTE_SRC("FALSE") 43 | ) primary ( 44 | .OQ(tmds_plus_clock[i]), 45 | .OFB(), 46 | .TQ(), 47 | .TFB(), 48 | .SHIFTOUT1(), 49 | .SHIFTOUT2(), 50 | .TBYTEOUT(), 51 | .CLK(clk_pixel_x5), 52 | .CLKDIV(clk_pixel), 53 | .D1(tmds_internal_plus_clock[i][0]), 54 | .D2(tmds_internal_plus_clock[i][1]), 55 | .D3(tmds_internal_plus_clock[i][2]), 56 | .D4(tmds_internal_plus_clock[i][3]), 57 | .D5(tmds_internal_plus_clock[i][4]), 58 | .D6(tmds_internal_plus_clock[i][5]), 59 | .D7(tmds_internal_plus_clock[i][6]), 60 | .D8(tmds_internal_plus_clock[i][7]), 61 | .TCE(1'b0), 62 | .OCE(1'b1), 63 | .TBYTEIN(1'b0), 64 | .RST(reset || internal_reset), 65 | .SHIFTIN1(cascade[i][0]), 66 | .SHIFTIN2(cascade[i][1]), 67 | .T1(1'b0), 68 | .T2(1'b0), 69 | .T3(1'b0), 70 | .T4(1'b0) 71 | ); 72 | OSERDESE2 #( 73 | .DATA_RATE_OQ("DDR"), 74 | .DATA_RATE_TQ("SDR"), 75 | .DATA_WIDTH(10), 76 | .SERDES_MODE("SLAVE"), 77 | .TRISTATE_WIDTH(1), 78 | .TBYTE_CTL("FALSE"), 79 | .TBYTE_SRC("FALSE") 80 | ) secondary ( 81 | .OQ(), 82 | .OFB(), 83 | .TQ(), 84 | .TFB(), 85 | .SHIFTOUT1(cascade[i][0]), 86 | .SHIFTOUT2(cascade[i][1]), 87 | .TBYTEOUT(), 88 | .CLK(clk_pixel_x5), 89 | .CLKDIV(clk_pixel), 90 | .D1(1'b0), 91 | .D2(1'b0), 92 | .D3(tmds_internal_plus_clock[i][8]), 93 | .D4(tmds_internal_plus_clock[i][9]), 94 | .D5(1'b0), 95 | .D6(1'b0), 96 | .D7(1'b0), 97 | .D8(1'b0), 98 | .TCE(1'b0), 99 | .OCE(1'b1), 100 | .TBYTEIN(1'b0), 101 | .RST(reset || internal_reset), 102 | .SHIFTIN1(1'b0), 103 | .SHIFTIN2(1'b0), 104 | .T1(1'b0), 105 | .T2(1'b0), 106 | .T3(1'b0), 107 | .T4(1'b0) 108 | ); 109 | end 110 | endgenerate 111 | `endif 112 | `elsif GW_IDE 113 | OSER10 gwSer0( 114 | .Q( tmds[ 0 ] ), 115 | .D0( tmds_internal[ 0 ][ 0 ] ), 116 | .D1( tmds_internal[ 0 ][ 1 ] ), 117 | .D2( tmds_internal[ 0 ][ 2 ] ), 118 | .D3( tmds_internal[ 0 ][ 3 ] ), 119 | .D4( tmds_internal[ 0 ][ 4 ] ), 120 | .D5( tmds_internal[ 0 ][ 5 ] ), 121 | .D6( tmds_internal[ 0 ][ 6 ] ), 122 | .D7( tmds_internal[ 0 ][ 7 ] ), 123 | .D8( tmds_internal[ 0 ][ 8 ] ), 124 | .D9( tmds_internal[ 0 ][ 9 ] ), 125 | .PCLK( clk_pixel ), 126 | .FCLK( clk_pixel_x5 ), 127 | .RESET( reset ) ); 128 | 129 | OSER10 gwSer1( 130 | .Q( tmds[ 1 ] ), 131 | .D0( tmds_internal[ 1 ][ 0 ] ), 132 | .D1( tmds_internal[ 1 ][ 1 ] ), 133 | .D2( tmds_internal[ 1 ][ 2 ] ), 134 | .D3( tmds_internal[ 1 ][ 3 ] ), 135 | .D4( tmds_internal[ 1 ][ 4 ] ), 136 | .D5( tmds_internal[ 1 ][ 5 ] ), 137 | .D6( tmds_internal[ 1 ][ 6 ] ), 138 | .D7( tmds_internal[ 1 ][ 7 ] ), 139 | .D8( tmds_internal[ 1 ][ 8 ] ), 140 | .D9( tmds_internal[ 1 ][ 9 ] ), 141 | .PCLK( clk_pixel ), 142 | .FCLK( clk_pixel_x5 ), 143 | .RESET( reset ) ); 144 | 145 | OSER10 gwSer2( 146 | .Q( tmds[ 2 ] ), 147 | .D0( tmds_internal[ 2 ][ 0 ] ), 148 | .D1( tmds_internal[ 2 ][ 1 ] ), 149 | .D2( tmds_internal[ 2 ][ 2 ] ), 150 | .D3( tmds_internal[ 2 ][ 3 ] ), 151 | .D4( tmds_internal[ 2 ][ 4 ] ), 152 | .D5( tmds_internal[ 2 ][ 5 ] ), 153 | .D6( tmds_internal[ 2 ][ 6 ] ), 154 | .D7( tmds_internal[ 2 ][ 7 ] ), 155 | .D8( tmds_internal[ 2 ][ 8 ] ), 156 | .D9( tmds_internal[ 2 ][ 9 ] ), 157 | .PCLK( clk_pixel ), 158 | .FCLK( clk_pixel_x5 ), 159 | .RESET( reset ) ); 160 | 161 | assign tmds_clock = clk_pixel; 162 | 163 | `else 164 | logic [9:0] tmds_reversed [NUM_CHANNELS-1:0]; 165 | genvar i, j; 166 | generate 167 | for (i = 0; i < NUM_CHANNELS; i++) 168 | begin: tmds_rev 169 | for (j = 0; j < 10; j++) 170 | begin: tmds_rev_channel 171 | assign tmds_reversed[i][j] = tmds_internal[i][9-j]; 172 | end 173 | end 174 | endgenerate 175 | `ifdef MODEL_TECH 176 | logic [3:0] position = 4'd0; 177 | always_ff @(posedge clk_pixel_x5) 178 | begin 179 | tmds <= {tmds_reversed[2][position], tmds_reversed[1][position], tmds_reversed[0][position]}; 180 | tmds_clock <= position >= 4'd5; 181 | position <= position == 4'd9 ? 4'd0 : position + 1'd1; 182 | end 183 | always_ff @(negedge clk_pixel_x5) 184 | begin 185 | tmds <= {tmds_reversed[2][position], tmds_reversed[1][position], tmds_reversed[0][position]}; 186 | tmds_clock <= position >= 4'd5; 187 | position <= position == 4'd9 ? 4'd0 : position + 1'd1; 188 | end 189 | `else 190 | `ifdef ALTERA_RESERVED_QIS 191 | altlvds_tx ALTLVDS_TX_component ( 192 | .tx_in ({10'b1111100000, tmds_reversed[2], tmds_reversed[1], tmds_reversed[0]}), 193 | .tx_inclock (clk_pixel_x5), 194 | .tx_out ({tmds_clock, tmds[2], tmds[1], tmds[0]}), 195 | .tx_outclock (), 196 | .pll_areset (1'b0), 197 | .sync_inclock (1'b0), 198 | .tx_coreclock (), 199 | .tx_data_reset (reset), 200 | .tx_enable (1'b1), 201 | .tx_locked (), 202 | .tx_pll_enable (1'b1), 203 | .tx_syncclock (clk_pixel)); 204 | defparam 205 | ALTLVDS_TX_component.center_align_msb = "UNUSED", 206 | ALTLVDS_TX_component.common_rx_tx_pll = "OFF", 207 | ALTLVDS_TX_component.coreclock_divide_by = 1, 208 | // ALTLVDS_TX_component.data_rate = "800.0 Mbps", 209 | ALTLVDS_TX_component.deserialization_factor = 10, 210 | ALTLVDS_TX_component.differential_drive = 0, 211 | ALTLVDS_TX_component.enable_clock_pin_mode = "UNUSED", 212 | ALTLVDS_TX_component.implement_in_les = "OFF", 213 | ALTLVDS_TX_component.inclock_boost = 0, 214 | ALTLVDS_TX_component.inclock_data_alignment = "EDGE_ALIGNED", 215 | ALTLVDS_TX_component.inclock_period = int'(10000000.0 / (VIDEO_RATE * 10.0)), 216 | ALTLVDS_TX_component.inclock_phase_shift = 0, 217 | // ALTLVDS_TX_component.intended_device_family = "Cyclone V", 218 | ALTLVDS_TX_component.lpm_hint = "CBX_MODULE_PREFIX=altlvds_tx_inst", 219 | ALTLVDS_TX_component.lpm_type = "altlvds_tx", 220 | ALTLVDS_TX_component.multi_clock = "OFF", 221 | ALTLVDS_TX_component.number_of_channels = 4, 222 | // ALTLVDS_TX_component.outclock_alignment = "EDGE_ALIGNED", 223 | // ALTLVDS_TX_component.outclock_divide_by = 1, 224 | // ALTLVDS_TX_component.outclock_duty_cycle = 50, 225 | // ALTLVDS_TX_component.outclock_multiply_by = 1, 226 | // ALTLVDS_TX_component.outclock_phase_shift = 0, 227 | // ALTLVDS_TX_component.outclock_resource = "Dual-Regional clock", 228 | ALTLVDS_TX_component.output_data_rate = int'(VIDEO_RATE * 10.0), 229 | ALTLVDS_TX_component.pll_compensation_mode = "AUTO", 230 | ALTLVDS_TX_component.pll_self_reset_on_loss_lock = "OFF", 231 | ALTLVDS_TX_component.preemphasis_setting = 0, 232 | // ALTLVDS_TX_component.refclk_frequency = "20.000000 MHz", 233 | ALTLVDS_TX_component.registered_input = "OFF", 234 | ALTLVDS_TX_component.use_external_pll = "ON", 235 | ALTLVDS_TX_component.use_no_phase_shift = "ON", 236 | ALTLVDS_TX_component.vod_setting = 0, 237 | ALTLVDS_TX_component.clk_src_is_pll = "off"; 238 | `else 239 | // We don't know what the platform is so the best bet is an IP-less implementation. 240 | // Shift registers are loaded with a set of values from tmds_channels every clk_pixel. 241 | // They are shifted out on clk_pixel_x5 by the time the next set is loaded. 242 | logic [9:0] tmds_shift [NUM_CHANNELS-1:0] = '{10'd0, 10'd0, 10'd0}; 243 | 244 | logic tmds_control = 1'd0; 245 | always_ff @(posedge clk_pixel) 246 | tmds_control <= !tmds_control; 247 | 248 | logic [3:0] tmds_control_synchronizer_chain = 4'd0; 249 | always_ff @(posedge clk_pixel_x5) 250 | tmds_control_synchronizer_chain <= {tmds_control, tmds_control_synchronizer_chain[3:1]}; 251 | 252 | logic load; 253 | assign load = tmds_control_synchronizer_chain[1] ^ tmds_control_synchronizer_chain[0]; 254 | logic [9:0] tmds_mux [NUM_CHANNELS-1:0]; 255 | always_comb 256 | begin 257 | if (load) 258 | tmds_mux = tmds_internal; 259 | else 260 | tmds_mux = tmds_shift; 261 | end 262 | 263 | // See Section 5.4.1 264 | for (i = 0; i < NUM_CHANNELS; i++) 265 | begin: tmds_shifting 266 | always_ff @(posedge clk_pixel_x5) 267 | tmds_shift[i] <= load ? tmds_mux[i] : tmds_shift[i] >> 2; 268 | end 269 | 270 | logic [9:0] tmds_shift_clk_pixel = 10'b0000011111; 271 | always_ff @(posedge clk_pixel_x5) 272 | tmds_shift_clk_pixel <= load ? 10'b0000011111 : {tmds_shift_clk_pixel[1:0], tmds_shift_clk_pixel[9:2]}; 273 | 274 | logic [NUM_CHANNELS-1:0] tmds_shift_negedge_temp; 275 | for (i = 0; i < NUM_CHANNELS; i++) 276 | begin: tmds_driving 277 | always_ff @(posedge clk_pixel_x5) 278 | begin 279 | tmds[i] <= tmds_shift[i][0]; 280 | tmds_shift_negedge_temp[i] <= tmds_shift[i][1]; 281 | end 282 | always_ff @(negedge clk_pixel_x5) 283 | tmds[i] <= tmds_shift_negedge_temp[i]; 284 | end 285 | logic tmds_clock_negedge_temp; 286 | always_ff @(posedge clk_pixel_x5) 287 | begin 288 | tmds_clock <= tmds_shift_clk_pixel[0]; 289 | tmds_clock_negedge_temp <= tmds_shift_clk_pixel[1]; 290 | end 291 | always_ff @(negedge clk_pixel_x5) 292 | tmds_clock <= tmds_shift_negedge_temp; 293 | 294 | `endif 295 | `endif 296 | `endif 297 | `endif 298 | endmodule 299 | -------------------------------------------------------------------------------- /src/source_product_description_info_frame.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI SPD InfoFrame packet. 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | // See CEA-861-D Section 6.5 page 72 (84 in PDF) 5 | module source_product_description_info_frame 6 | #( 7 | parameter bit [8*8-1:0] VENDOR_NAME = 0, 8 | parameter bit [8*16-1:0] PRODUCT_DESCRIPTION = 0, 9 | parameter bit [7:0] SOURCE_DEVICE_INFORMATION = 0 10 | ) 11 | ( 12 | output logic [23:0] header, 13 | output logic [55:0] sub [3:0] 14 | ); 15 | 16 | localparam bit [4:0] LENGTH = 5'd25; 17 | localparam bit [7:0] VERSION = 8'd1; 18 | localparam bit [6:0] TYPE = 7'd3; 19 | 20 | assign header = {{3'b0, LENGTH}, VERSION, {1'b1, TYPE}}; 21 | 22 | // PB0-PB6 = sub0 23 | // PB7-13 = sub1 24 | // PB14-20 = sub2 25 | // PB21-27 = sub3 26 | logic [7:0] packet_bytes [27:0]; 27 | 28 | assign packet_bytes[0] = 8'd1 + ~(header[23:16] + header[15:8] + header[7:0] + packet_bytes[25] + packet_bytes[24] + packet_bytes[23] + packet_bytes[22] + packet_bytes[21] + packet_bytes[20] + packet_bytes[19] + packet_bytes[18] + packet_bytes[17] + packet_bytes[16] + packet_bytes[15] + packet_bytes[14] + packet_bytes[13] + packet_bytes[12] + packet_bytes[11] + packet_bytes[10] + packet_bytes[9] + packet_bytes[8] + packet_bytes[7] + packet_bytes[6] + packet_bytes[5] + packet_bytes[4] + packet_bytes[3] + packet_bytes[2] + packet_bytes[1]); 29 | 30 | 31 | byte vendor_name [0:7]; 32 | byte product_description [0:15]; 33 | 34 | genvar i; 35 | generate 36 | for (i = 0; i < 8; i++) 37 | begin: vendor_to_bytes 38 | assign vendor_name[i] = VENDOR_NAME[(7-i+1)*8-1:(7-i)*8]; 39 | end 40 | for (i = 0; i < 16; i++) 41 | begin: product_to_bytes 42 | assign product_description[i] = PRODUCT_DESCRIPTION[(15-i+1)*8-1:(15-i)*8]; 43 | end 44 | 45 | for (i = 1; i < 9; i++) 46 | begin: pb_vendor 47 | assign packet_bytes[i] = vendor_name[i - 1] == 8'h30 ? 8'h00 : vendor_name[i - 1]; 48 | end 49 | for (i = 9; i < LENGTH; i++) 50 | begin: pb_product 51 | assign packet_bytes[i] = product_description[i - 9] == 8'h30 ? 8'h00 : product_description[i - 9]; 52 | end 53 | assign packet_bytes[LENGTH] = SOURCE_DEVICE_INFORMATION; 54 | for (i = 26; i < 28; i++) 55 | begin: pb_reserved 56 | assign packet_bytes[i] = 8'd0; 57 | end 58 | for (i = 0; i < 4; i++) 59 | begin: pb_to_sub 60 | assign sub[i] = {packet_bytes[6 + i*7], packet_bytes[5 + i*7], packet_bytes[4 + i*7], packet_bytes[3 + i*7], packet_bytes[2 + i*7], packet_bytes[1 + i*7], packet_bytes[0 + i*7]}; 61 | end 62 | endgenerate 63 | 64 | endmodule 65 | -------------------------------------------------------------------------------- /src/tmds_channel.sv: -------------------------------------------------------------------------------- 1 | // Implementation of HDMI Spec v1.4a Section 5.4: Encoding, Section 5.2.2.1: Video Guard Band, Section 5.2.3.3: Data Island Guard Bands. 2 | // By Sameer Puri https://github.com/sameer 3 | 4 | module tmds_channel 5 | #( 6 | // TMDS Channel number. 7 | // There are only 3 possible channel numbers in HDMI 1.4a: 0, 1, 2. 8 | parameter int CN = 0 9 | ) 10 | ( 11 | input logic clk_pixel, 12 | input logic [7:0] video_data, 13 | input logic [3:0] data_island_data, 14 | input logic [1:0] control_data, 15 | input logic [2:0] mode, // Mode select (0 = control, 1 = video, 2 = video guard, 3 = island, 4 = island guard) 16 | output logic [9:0] tmds = 10'b1101010100 17 | ); 18 | 19 | // See Section 5.4.4.1 20 | // Below is a direct implementation of Figure 5-7, using the same variable names. 21 | 22 | logic signed [4:0] acc = 5'sd0; 23 | 24 | logic [8:0] q_m; 25 | logic [9:0] q_out; 26 | logic [9:0] video_coding; 27 | assign video_coding = q_out; 28 | 29 | logic [3:0] N1D; 30 | logic signed [4:0] N1q_m07; 31 | logic signed [4:0] N0q_m07; 32 | always_comb 33 | begin 34 | N1D = video_data[0] + video_data[1] + video_data[2] + video_data[3] + video_data[4] + video_data[5] + video_data[6] + video_data[7]; 35 | case(q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]) 36 | 4'b0000: N1q_m07 = 5'sd0; 37 | 4'b0001: N1q_m07 = 5'sd1; 38 | 4'b0010: N1q_m07 = 5'sd2; 39 | 4'b0011: N1q_m07 = 5'sd3; 40 | 4'b0100: N1q_m07 = 5'sd4; 41 | 4'b0101: N1q_m07 = 5'sd5; 42 | 4'b0110: N1q_m07 = 5'sd6; 43 | 4'b0111: N1q_m07 = 5'sd7; 44 | 4'b1000: N1q_m07 = 5'sd8; 45 | default: N1q_m07 = 5'sd0; 46 | endcase 47 | N0q_m07 = 5'sd8 - N1q_m07; 48 | end 49 | 50 | logic signed [4:0] acc_add; 51 | 52 | integer i; 53 | 54 | always_comb 55 | begin 56 | if (N1D > 4'd4 || (N1D == 4'd4 && video_data[0] == 1'd0)) 57 | begin 58 | q_m[0] = video_data[0]; 59 | for(i = 0; i < 7; i++) 60 | q_m[i + 1] = q_m[i] ~^ video_data[i + 1]; 61 | q_m[8] = 1'b0; 62 | end 63 | else 64 | begin 65 | q_m[0] = video_data[0]; 66 | for(i = 0; i < 7; i++) 67 | q_m[i + 1] = q_m[i] ^ video_data[i + 1]; 68 | q_m[8] = 1'b1; 69 | end 70 | if (acc == 5'sd0 || (N1q_m07 == N0q_m07)) 71 | begin 72 | if (q_m[8]) 73 | begin 74 | acc_add = N1q_m07 - N0q_m07; 75 | q_out = {~q_m[8], q_m[8], q_m[7:0]}; 76 | end 77 | else 78 | begin 79 | acc_add = N0q_m07 - N1q_m07; 80 | q_out = {~q_m[8], q_m[8], ~q_m[7:0]}; 81 | end 82 | end 83 | else 84 | begin 85 | if ((acc > 5'sd0 && N1q_m07 > N0q_m07) || (acc < 5'sd0 && N1q_m07 < N0q_m07)) 86 | begin 87 | q_out = {1'b1, q_m[8], ~q_m[7:0]}; 88 | acc_add = (N0q_m07 - N1q_m07) + (q_m[8] ? 5'sd2 : 5'sd0); 89 | end 90 | else 91 | begin 92 | q_out = {1'b0, q_m[8], q_m[7:0]}; 93 | acc_add = (N1q_m07 - N0q_m07) - (~q_m[8] ? 5'sd2 : 5'sd0); 94 | end 95 | end 96 | end 97 | 98 | always_ff @(posedge clk_pixel) acc <= mode != 3'd1 ? 5'sd0 : acc + acc_add; 99 | 100 | // See Section 5.4.2 101 | logic [9:0] control_coding; 102 | always_comb 103 | begin 104 | unique case(control_data) 105 | 2'b00: control_coding = 10'b1101010100; 106 | 2'b01: control_coding = 10'b0010101011; 107 | 2'b10: control_coding = 10'b0101010100; 108 | 2'b11: control_coding = 10'b1010101011; 109 | endcase 110 | end 111 | 112 | // See Section 5.4.3 113 | logic [9:0] terc4_coding; 114 | always_comb 115 | begin 116 | unique case(data_island_data) 117 | 4'b0000 : terc4_coding = 10'b1010011100; 118 | 4'b0001 : terc4_coding = 10'b1001100011; 119 | 4'b0010 : terc4_coding = 10'b1011100100; 120 | 4'b0011 : terc4_coding = 10'b1011100010; 121 | 4'b0100 : terc4_coding = 10'b0101110001; 122 | 4'b0101 : terc4_coding = 10'b0100011110; 123 | 4'b0110 : terc4_coding = 10'b0110001110; 124 | 4'b0111 : terc4_coding = 10'b0100111100; 125 | 4'b1000 : terc4_coding = 10'b1011001100; 126 | 4'b1001 : terc4_coding = 10'b0100111001; 127 | 4'b1010 : terc4_coding = 10'b0110011100; 128 | 4'b1011 : terc4_coding = 10'b1011000110; 129 | 4'b1100 : terc4_coding = 10'b1010001110; 130 | 4'b1101 : terc4_coding = 10'b1001110001; 131 | 4'b1110 : terc4_coding = 10'b0101100011; 132 | 4'b1111 : terc4_coding = 10'b1011000011; 133 | endcase 134 | end 135 | 136 | // See Section 5.2.2.1 137 | logic [9:0] video_guard_band; 138 | generate 139 | if (CN == 0 || CN == 2) 140 | assign video_guard_band = 10'b1011001100; 141 | else 142 | assign video_guard_band = 10'b0100110011; 143 | endgenerate 144 | 145 | // See Section 5.2.3.3 146 | logic [9:0] data_guard_band; 147 | generate 148 | if (CN == 1 || CN == 2) 149 | assign data_guard_band = 10'b0100110011; 150 | else 151 | assign data_guard_band = control_data == 2'b00 ? 10'b1010001110 152 | : control_data == 2'b01 ? 10'b1001110001 153 | : control_data == 2'b10 ? 10'b0101100011 154 | : 10'b1011000011; 155 | endgenerate 156 | 157 | // Apply selected mode. 158 | always @(posedge clk_pixel) 159 | begin 160 | case (mode) 161 | 3'd0: tmds <= control_coding; 162 | 3'd1: tmds <= video_coding; 163 | 3'd2: tmds <= video_guard_band; 164 | 3'd3: tmds <= terc4_coding; 165 | 3'd4: tmds <= data_guard_band; 166 | endcase 167 | end 168 | 169 | endmodule 170 | -------------------------------------------------------------------------------- /test/audio_clock_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "audio_clock_tb.sv", 3 | ] 4 | 5 | modules = { 6 | "local" : [ "../../src/" ], 7 | } 8 | -------------------------------------------------------------------------------- /test/audio_clock_tb/audio_clock_tb.sv: -------------------------------------------------------------------------------- 1 | module audio_clock_tb (); 2 | 3 | localparam AUDIO_RATE = 48000; 4 | 5 | hdmi #(.VIDEO_ID_CODE(1), .AUDIO_RATE(AUDIO_RATE), .VIDEO_REFRESH_RATE(59.94)) hdmi_640x480_60Hz(); 6 | hdmi #(.VIDEO_ID_CODE(1), .AUDIO_RATE(AUDIO_RATE), .VIDEO_REFRESH_RATE(60)) hdmi_640x480_59_94Hz(); 7 | hdmi #(.VIDEO_ID_CODE(4), .AUDIO_RATE(AUDIO_RATE), .VIDEO_REFRESH_RATE(59.94)) hdmi_1280x720_59_94Hz(); 8 | hdmi #(.VIDEO_ID_CODE(16), .AUDIO_RATE(AUDIO_RATE), .VIDEO_REFRESH_RATE(60)) hdmi_1920x1080_60Hz(); 9 | hdmi #(.VIDEO_ID_CODE(17), .AUDIO_RATE(AUDIO_RATE), .VIDEO_REFRESH_RATE(60)) hdmi_720x576_50Hz(); 10 | 11 | initial 12 | begin 13 | assert(hdmi_640x480_60Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.N == 6144); 14 | assert(hdmi_640x480_60Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.CYCLE_TIME_STAMP_COUNTER_IDEAL == 25175); 15 | assert(hdmi_640x480_59_94Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.N == 6144); 16 | assert(hdmi_640x480_59_94Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.CYCLE_TIME_STAMP_COUNTER_IDEAL == 25200); 17 | assert(hdmi_1280x720_59_94Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.N == 6144); 18 | assert(hdmi_1280x720_59_94Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.CYCLE_TIME_STAMP_COUNTER_IDEAL == 74176); 19 | assert(hdmi_1920x1080_60Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.N == 6144); 20 | assert(hdmi_1920x1080_60Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.CYCLE_TIME_STAMP_COUNTER_IDEAL == 148500); 21 | assert(hdmi_720x576_50Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.N == 6144); 22 | assert(hdmi_720x576_50Hz.true_hdmi_output.packet_picker.audio_clock_regeneration_packet.CYCLE_TIME_STAMP_COUNTER_IDEAL == 27000); 23 | $finish; 24 | end 25 | 26 | endmodule 27 | -------------------------------------------------------------------------------- /test/audio_param_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "audio_param_tb.sv", 3 | ] 4 | 5 | modules = { 6 | "local" : [ "../../src/" ], 7 | } 8 | -------------------------------------------------------------------------------- /test/audio_param_tb/audio_param_tb.sv: -------------------------------------------------------------------------------- 1 | module audio_param_tb (); 2 | 3 | localparam AUDIO_RATE = 48000; 4 | 5 | hdmi #(.AUDIO_RATE(AUDIO_RATE), .AUDIO_BIT_WIDTH(16)) hdmi_48khz_16bit(); 6 | hdmi #(.AUDIO_RATE(AUDIO_RATE), .AUDIO_BIT_WIDTH(20)) hdmi_48khz_20bit(); 7 | hdmi #(.AUDIO_RATE(AUDIO_RATE), .AUDIO_BIT_WIDTH(24)) hdmi_48khz_24bit(); 8 | 9 | initial 10 | begin 11 | assert (hdmi_48khz_16bit.true_hdmi_output.packet_picker.audio_sample_packet.WORD_LENGTH == 4'b0010) else $fatal(1, "Incorrect word length"); 12 | assert (hdmi_48khz_16bit.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left[35:32] == 4'b0010) else $fatal(1, "Incorrect word length %b", hdmi_48khz_16bit.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left); 13 | 14 | assert (hdmi_48khz_20bit.true_hdmi_output.packet_picker.audio_sample_packet.WORD_LENGTH == 4'b1010) else $fatal(1, "Incorrect word length"); 15 | assert (hdmi_48khz_20bit.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left[35:32] == 4'b1010) else $fatal(1, "Incorrect word length"); 16 | assert (hdmi_48khz_24bit.true_hdmi_output.packet_picker.audio_sample_packet.WORD_LENGTH == 4'b1011) else $fatal(1, "Incorrect word length"); 17 | assert (hdmi_48khz_24bit.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left[35:32] == 4'b1011) else $fatal(1, "Incorrect word length"); 18 | $finish; 19 | end 20 | 21 | endmodule 22 | -------------------------------------------------------------------------------- /test/spd_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "spd_tb.sv", 3 | ] 4 | 5 | modules = { 6 | "local" : [ "../../src/" ], 7 | } 8 | -------------------------------------------------------------------------------- /test/spd_tb/spd_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1 ps / 1 ps 2 | 3 | module spd_tb(); 4 | 5 | logic [55:0] sub [3:0]; 6 | 7 | source_product_description_info_frame #( 8 | .VENDOR_NAME({"Unknown", 8'h00}), 9 | .PRODUCT_DESCRIPTION({"FPGA", 96'd0}), 10 | .SOURCE_DEVICE_INFORMATION(8'h00) 11 | ) source_product_description_info_frame (.sub(sub)); 12 | 13 | initial 14 | begin 15 | #1ps; 16 | assert(source_product_description_info_frame.vendor_name == '{8'h55, 8'h6e, 8'h6b, 8'h6e, 8'h6f, 8'h77, 8'h6e, 8'h00}) else $fatal(0, "%p", source_product_description_info_frame.vendor_name); 17 | assert(source_product_description_info_frame.product_description == '{8'h46, 8'h50, 8'h47, 8'h41, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00}); 18 | assert(sub[0] == {"wonknU", 8'h54}) else $fatal(0, "%h expected %h", sub[0], {"wonknU", 8'h54}); 19 | assert(sub[1] == {8'd0, "AGPF", 8'd0, "n"}) else $fatal(0, "%h expected %h", sub[1], {8'd0, "AGPF", 8'd0, "n"}); 20 | assert(sub[2] == 56'd0) else $fatal(0, "subpacket 3 should be 0"); 21 | $finish; 22 | end 23 | 24 | endmodule 25 | -------------------------------------------------------------------------------- /test/top_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "top_tb.sv", 3 | "pll.sv" 4 | ] 5 | 6 | modules = { 7 | "local" : [ "../../top/" ], 8 | } 9 | -------------------------------------------------------------------------------- /test/top_tb/pll.sv: -------------------------------------------------------------------------------- 1 | // This is a FAKE PLL! You should be using the PLL IP available from your FPGA vendor 2 | 3 | timeunit 1ps; 4 | timeprecision 1ps; 5 | 6 | module pll ( 7 | output logic c0 = 0, 8 | output logic c1 = 0, 9 | output logic c2 = 0 10 | ); 11 | always 12 | begin 13 | #7937ps c0 = 1; 14 | #7937ps c0 = 0; 15 | end 16 | 17 | always 18 | begin 19 | #39683ps c1 = 1; 20 | #39683ps c1 = 0; 21 | end 22 | 23 | always 24 | begin 25 | #20833333ps c2 = 1; 26 | #20833333ps c2 = 0; 27 | end 28 | 29 | endmodule 30 | -------------------------------------------------------------------------------- /test/top_tb/top_tb.sv: -------------------------------------------------------------------------------- 1 | module top_tb(); 2 | 3 | timeunit 1ns; 4 | timeprecision 1ns; 5 | 6 | initial begin 7 | #40ms $finish; 8 | end 9 | 10 | top top (); 11 | 12 | logic [9:0] cx = 800 - 4; 13 | logic [9:0] cy = 525 - 1; 14 | 15 | logic [9:0] tmds_values [2:0] = '{10'dx, 10'dx, 10'dx}; 16 | 17 | 18 | logic [7:0] decoded_values [2:0]; 19 | logic [3:0] decoded_terc4_values [2:0]; 20 | genvar i; 21 | genvar j; 22 | generate 23 | for (j = 0; j < 3; j++) 24 | begin 25 | assign decoded_values[j][0] = tmds_values[j][9] ? ~tmds_values[j][0] : tmds_values[j][0]; 26 | for (i = 1; i < 8; i++) 27 | begin 28 | assign decoded_values[j][i] = tmds_values[j][8] ? 29 | (tmds_values[j][9] ? (~tmds_values[j][i]) ^ (~tmds_values[j][i-1]) : tmds_values[j][i] ^ tmds_values[j][i-1]) 30 | : (tmds_values[j][9] ? (~tmds_values[j][i]) ~^ (~tmds_values[j][i-1]) : tmds_values[j][i] ~^ tmds_values[j][i-1]); 31 | end 32 | assign decoded_terc4_values[j] = tmds_values[j] == 10'b1010011100 ? 4'b0000 33 | : tmds_values[j] == 10'b1001100011 ? 4'b0001 34 | : tmds_values[j] == 10'b1011100100 ? 4'b0010 35 | : tmds_values[j] == 10'b1011100010 ? 4'b0011 36 | : tmds_values[j] == 10'b0101110001 ? 4'b0100 37 | : tmds_values[j] == 10'b0100011110 ? 4'b0101 38 | : tmds_values[j] == 10'b0110001110 ? 4'b0110 39 | : tmds_values[j] == 10'b0100111100 ? 4'b0111 40 | : tmds_values[j] == 10'b1011001100 ? 4'b1000 41 | : tmds_values[j] == 10'b0100111001 ? 4'b1001 42 | : tmds_values[j] == 10'b0110011100 ? 4'b1010 43 | : tmds_values[j] == 10'b1011000110 ? 4'b1011 44 | : tmds_values[j] == 10'b1010001110 ? 4'b1100 45 | : tmds_values[j] == 10'b1001110001 ? 4'b1101 46 | : tmds_values[j] == 10'b0101100011 ? 4'b1110 47 | : tmds_values[j] == 10'b1011000011 ? 4'b1111 48 | : 4'bzzzz; 49 | end 50 | endgenerate 51 | 52 | always @(posedge top.clk_pixel) 53 | begin 54 | tmds_values[0] <= top.hdmi.tmds_internal[0]; 55 | tmds_values[1] <= top.hdmi.tmds_internal[1]; 56 | tmds_values[2] <= top.hdmi.tmds_internal[2]; 57 | end 58 | 59 | logic [4:0] data_counter = 0; 60 | logic [63:0] sub [3:0] = '{64'dX, 64'dX, 64'dX, 64'dX}; 61 | logic [31:0] header = 32'dX; 62 | 63 | logic [19:0] N; 64 | assign N = {sub[0][35:32], sub[0][47:40], sub[0][55:48]}; 65 | logic [19:0] CTS; 66 | assign CTS = {sub[0][11:8], sub[0][23:16], sub[0][31:24]}; 67 | 68 | logic [23:0] L [3:0]; 69 | logic [23:0] R [3:0]; 70 | logic [3:0] PCUVr [3:0]; 71 | logic [3:0] PCUVl [3:0]; 72 | generate 73 | for (i = 0; i< 4; i++) 74 | begin 75 | assign R[i] = sub[i][47:24]; 76 | assign L[i] = sub[i][23:0]; 77 | assign PCUVr[i] = sub[i][55:52]; 78 | assign PCUVl[i] = sub[i][51:48]; 79 | end 80 | endgenerate 81 | logic [2:0] num_samples_present; 82 | assign num_samples_present = 3'(header[11]) + header[10] + header[9] + header[8]; 83 | logic [$clog2(192)-1:0] frame_counter = 0; 84 | logic [191:0] channel_status [1:0] = '{192'dX, 192'dX}; 85 | 86 | // PB0-PB6 = sub0 87 | // PB7-13 = sub1 88 | // PB14-20 = sub2 89 | // PB21-27 = sub3 90 | 91 | // returns number of non-zero elements in array 92 | function int check_non_zero_elem(logic [7:0] a0[]); 93 | check_non_zero_elem = 0; 94 | for(int i=$low(a0); i <= $high(a0); i++) 95 | if(a0[i] != 0) check_non_zero_elem++; 96 | endfunction 97 | 98 | logic [7:0] packet_bytes [0:27]; 99 | for (i = 0; i < 28; i++) 100 | begin 101 | assign packet_bytes[i] = sub[i / 7][((i % 7) + 1) * 8 - 1 :(i % 7) * 8]; 102 | end 103 | 104 | logic first_packet = 1; 105 | logic first_audio_packet = 1; 106 | logic [15:0] previous_sample [1:0]; 107 | 108 | integer k; 109 | always @(posedge top.clk_pixel) 110 | begin 111 | cx <= cx == top.frame_width - 1 ? 0 : cx + 1; 112 | cy <= cx == top.frame_width-1'b1 ? cy == top.frame_height-1'b1 ? 0 : cy + 1'b1 : cy; 113 | if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && (cx >= top.screen_width + 12 && cx < top.screen_width + 14) || (cx >= top.screen_width + 14 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 && cx < top.screen_width + 14 + top.hdmi.true_hdmi_output.num_packets_alongside * 32 + 2)) 114 | begin 115 | assert(tmds_values[2] == 10'b0100110011) else $fatal("Channel 2 DI GB incorrect: %b", tmds_values[2]); 116 | assert(tmds_values[1] == 10'b0100110011) else $fatal("Channel 1 DI GB incorrect"); 117 | assert(tmds_values[0] == 10'b1010001110 || tmds_values[0] == 10'b1001110001 || tmds_values[0] == 10'b0101100011 || tmds_values[0] == 10'b1011000011) else $fatal("Channel 0 DI GB incorrect"); 118 | end 119 | else if (top.hdmi.true_hdmi_output.num_packets_alongside > 0 && cx >= top.screen_width + 14 && cx < top.screen_width + 14 + top.hdmi.true_hdmi_output.num_packets_alongside * 32) 120 | begin 121 | data_counter <= data_counter + 1'd1; 122 | if (data_counter == 0) 123 | begin 124 | sub[3][63:1] <= 63'dX; 125 | sub[2][63:1] <= 63'dX; 126 | sub[1][63:1] <= 63'dX; 127 | sub[0][63:1] <= 63'dX; 128 | header[31:1] <= 31'dX; 129 | if (cx != top.screen_width + 14 || !first_packet) // Packet complete 130 | begin 131 | first_packet <= 0; 132 | case(header[7:0]) 133 | 8'h00: begin 134 | $display("NULL packet"); 135 | end 136 | 8'h01: begin 137 | $display("Audio Clock Regen packet N = %d, CTS = %d", N, CTS); 138 | assert(header[23:8] === 16'd0) else $fatal("Clock regen HB1, HB2 should be X: %b, %b", header[23:16], header[15:8]); 139 | assert(sub[0] == sub[1] && sub[1] == sub[2] && sub[2] == sub[3]) else $fatal("Clock regen subpackets are different"); 140 | assert(N == 128*48000/1000) else $fatal("Incorrect N: %d should be %d", N, 128*48000/1000); 141 | if (CTS == 24939) 142 | $warning("CTS is out of spec, this should only happen once while warming up at the beginning of the testbench."); 143 | assert(CTS == 25200 || CTS == 25199 || CTS == 24939) else $fatal("Incorrect CTS, should hover around 25200: %d", CTS); 144 | end 145 | 8'h02: begin 146 | $display("Audio Sample packet #%d - %d", frame_counter + 1, frame_counter + num_samples_present); 147 | assert(header[12] == 1'b0) else $fatal("Sample layout is not 2 channel"); 148 | assert(header[11:8] == 4'd15 || header[11:8] == 4'd7 || header[11:8] == 4'd3 || header[11:8] == 4'd1) else $fatal("Sample present flag values unexpected: %b", header[11:8]); 149 | assert(header[19:16] == 4'd0) else $fatal("Sample flat values nonzero: %b", header[19:16]); 150 | for (k = 0; k < 4; k++) 151 | begin 152 | if (!header[8 + k]) // Sample not present 153 | continue; 154 | channel_status[1][frame_counter + k] = PCUVr[k][2]; 155 | channel_status[0][frame_counter + k] = PCUVl[k][2]; 156 | if ((frame_counter + k) % 192 == 0) // Last frame was end of IEC60958 frame, this sample starts a new frame 157 | begin 158 | if (!first_audio_packet) 159 | begin 160 | assert(channel_status[0] == top.hdmi.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left) else $fatal("Incorrect left channel status: %h should be %h", channel_status[0], top.hdmi.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_left); 161 | assert(channel_status[1] == top.hdmi.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_right) else $fatal("Incorrect right channel status: %h should be %h", channel_status[1], top.hdmi.true_hdmi_output.packet_picker.audio_sample_packet.channel_status_right); 162 | assert(previous_sample[0] - 1'd1 == L[k][23:8]) else $fatal("Expected left channel sample to be 1 less than previous: %d - 1 != %d", previous_sample[0], L[k][23:8]); 163 | assert(previous_sample[1] + 1'd1 == R[k][23:8]) else $fatal("Expected right channel sample to be 1 greater than previous: %d + 1 != %d", previous_sample[1], R[k][23:8]); 164 | end 165 | first_audio_packet <= 0; 166 | assert(header[20 + k] == 1'b1) else $fatal("Sample B value low for sample %d with counter %d", k, frame_counter); 167 | end 168 | else 169 | assert(header[20 + k] == 1'b0) else $fatal("Sample B value high for sample %d with counter %d", k, frame_counter); 170 | assert(PCUVr[k][1] == 1'b0 && PCUVl[k][1] == 1'b0) else $fatal("Sample user data bits nonzero"); 171 | assert(PCUVr[k][0] == 1'b0 && PCUVl[k][0] == 1'b0) else $fatal("Sample validity bits nonzero"); 172 | assert({PCUVr[k][3:0], R[k]} % 2 == 0) else $fatal("Sample right parity not even: %b", {PCUVr[k], R[k]}); 173 | assert({PCUVl[k][3:0], L[k]} % 2 == 0) else $fatal("Sample left parity not even: %b", {PCUVl[k], L[k]}); 174 | previous_sample = '{R[k][23:8], L[k][23:8]}; 175 | end 176 | frame_counter <= (frame_counter + num_samples_present) % 192; 177 | end 178 | 8'h82: begin 179 | $display("AVI InfoFrame"); 180 | assert(packet_bytes.sum() + header[23:16] + header[15:8] + header[7:0] == 8'd0) else $fatal("Bad checksum"); 181 | assert(check_non_zero_elem(packet_bytes[14:27]) == 0) else $fatal("Reserved bytes not 0"); 182 | end 183 | 8'h83: begin 184 | $display("SPD InfoFrame this is a %s created by %s that is a 0x%h", string'(packet_bytes[9:24]), string'(packet_bytes[1:8]), packet_bytes[25]); 185 | assert(packet_bytes.sum() + header[23:16] + header[15:8] + header[7:0] == 8'd0) else $fatal("Bad checksum"); 186 | assert(check_non_zero_elem(packet_bytes[26:27]) == 0) else $fatal("Reserved bytes not 0"); 187 | end 188 | 8'h84: begin 189 | $display("Audio InfoFrame"); 190 | assert(packet_bytes.sum() + header[23:16] + header[15:8] + header[7:0] == 8'd0) else $fatal("Bad checksum"); 191 | assert(packet_bytes[1] == 8'd1) else $fatal("Only channel count of 2 should be set"); 192 | assert(packet_bytes[2] == 8'd0 && packet_bytes[3] == 8'd0) else $fatal("These are refer to stream header"); 193 | assert(check_non_zero_elem(packet_bytes[6:27]) == 0) else $fatal("Reserved bytes not 0"); 194 | end 195 | default: begin 196 | $fatal("Unhandled packet type %h (%s) at %d, %d: %p", header[7:0], "Unknown", cx, cy, sub); 197 | end 198 | endcase 199 | end 200 | end 201 | sub[3][{data_counter, 1'b1}] <= decoded_terc4_values[2][3]; 202 | sub[3][{data_counter, 1'b0}] <= decoded_terc4_values[1][3]; 203 | sub[2][{data_counter, 1'b1}] <= decoded_terc4_values[2][2]; 204 | sub[2][{data_counter, 1'b0}] <= decoded_terc4_values[1][2]; 205 | sub[1][{data_counter, 1'b1}] <= decoded_terc4_values[2][1]; 206 | sub[1][{data_counter, 1'b0}] <= decoded_terc4_values[1][1]; 207 | sub[0][{data_counter, 1'b1}] <= decoded_terc4_values[2][0]; 208 | sub[0][{data_counter, 1'b0}] <= decoded_terc4_values[1][0]; 209 | header[data_counter] <= decoded_terc4_values[0][2]; 210 | end 211 | end 212 | 213 | endmodule 214 | -------------------------------------------------------------------------------- /top/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "top.sv", 3 | ] 4 | 5 | modules = { 6 | "local": [ 7 | "../src" 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /top/top.sv: -------------------------------------------------------------------------------- 1 | module top (); 2 | logic [2:0] tmds; 3 | logic tmds_clock; 4 | 5 | logic clk_pixel; 6 | logic clk_pixel_x5; 7 | logic clk_audio; 8 | logic reset; 9 | 10 | pll pll(.c0(clk_pixel_x5), .c1(clk_pixel), .c2(clk_audio)); 11 | 12 | logic [15:0] audio_sample_word [1:0] = '{16'd0, 16'd0}; 13 | always @(posedge clk_audio) 14 | audio_sample_word <= '{audio_sample_word[1] + 16'd1, audio_sample_word[0] - 16'd1}; 15 | 16 | logic [23:0] rgb = 24'd0; 17 | logic [9:0] cx, cy, screen_start_x, screen_start_y, frame_width, frame_height, screen_width, screen_height; 18 | // Border test (left = red, top = green, right = blue, bottom = blue, fill = black) 19 | always @(posedge clk_pixel) 20 | rgb <= {cx == 0 ? ~8'd0 : 8'd0, cy == 0 ? ~8'd0 : 8'd0, cx == screen_width - 1'd1 || cy == screen_width - 1'd1 ? ~8'd0 : 8'd0}; 21 | 22 | // 640x480 @ 59.94Hz 23 | hdmi #(.VIDEO_ID_CODE(1), .VIDEO_REFRESH_RATE(59.94), .AUDIO_RATE(48000), .AUDIO_BIT_WIDTH(16)) hdmi( 24 | .clk_pixel_x5(clk_pixel_x5), 25 | .clk_pixel(clk_pixel), 26 | .clk_audio(clk_audio), 27 | .reset(reset), 28 | .rgb(rgb), 29 | .audio_sample_word(audio_sample_word), 30 | .tmds(tmds), 31 | .tmds_clock(tmds_clock), 32 | .cx(cx), 33 | .cy(cy), 34 | .frame_width(frame_width), 35 | .frame_height(frame_height), 36 | .screen_width(screen_width), 37 | .screen_height(screen_height) 38 | ); 39 | 40 | endmodule 41 | --------------------------------------------------------------------------------