├── .gitattributes ├── .gitignore ├── .rules.verible_lint ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── doc ├── block_diagram.svg ├── circuit_photo.jpg ├── ebu100.png ├── ebu75.png ├── framebuffer.md ├── lessons_learned.md ├── parrot.jpg ├── parrot_ntsc.png ├── parrot_pal.png ├── parrot_secam.png ├── secam_stresstest.png └── secam_stresstest_result.png ├── gowin ├── flash.sh ├── impl │ ├── .gitignore │ └── project_process_config.json ├── src │ ├── gowin_rpll │ │ ├── gowin_rpll.ipc │ │ ├── gowin_rpll.mod │ │ ├── gowin_rpll.v │ │ └── gowin_rpll_tmp.v │ ├── psram_memory_interface_hs_v2 │ │ ├── psram_memory_interface_hs_v2.ipc │ │ ├── psram_memory_interface_hs_v2.v │ │ ├── psram_memory_interface_hs_v2.vo │ │ └── psram_memory_interface_hs_v2_tmp.v │ ├── testpic_gen.cst │ └── testpic_gen.sdc ├── testpic_gen.gprj └── upload.sh ├── kicad └── circuit │ ├── circuit.kicad_pcb │ ├── circuit.kicad_prl │ ├── circuit.kicad_pro │ ├── circuit.kicad_sch │ ├── circuit.pdf │ └── circuit.svg ├── mem ├── secam_ampl.txt └── sinewave.txt ├── rtl ├── RGB2YCbCr.sv ├── burst_bus_arbiter.sv ├── burst_writer.sv ├── coefficients.svh ├── common.svh ├── composite_video_encoder.sv ├── configuration.svh ├── debug_busmaster.sv ├── delayfifo.sv ├── ebu75.sv ├── filter │ ├── filter_pal_chroma_lowpass.sv │ ├── filter_pal_luma.sv │ ├── filter_pal_ntsc_carrier.sv │ ├── filter_secam_amplitude_lowpass.sv │ ├── filter_secam_chroma_lowpass.sv │ └── filter_secam_deemphasis.sv ├── framebuffer.sv ├── logic_analyzer.sv ├── memory_tester.sv ├── pal_ntsc_encoder.sv ├── pixel_convolver.sv ├── qam.sv ├── rgbbars.sv ├── secam_ampl.sv ├── secam_encoder.sv ├── sinus.sv ├── top_dactest_frequency.sv ├── top_dactest_sawtooth.sv ├── top_testpic_generator.sv ├── uart_busmaster.sv ├── uart_rx.v ├── uart_tx.v └── video_timing.sv ├── sim ├── .gitignore ├── convolver.gtkw ├── filter_int_5tap.sv ├── filter_int_5tap_floorA.sv ├── pal_verify_chromafilter.v ├── pal_verify_lumafilter.sv ├── psram_emu.sv ├── secam.gtkw ├── sim_pixel_convolver.cpp ├── sim_pixel_convolver.sh ├── sim_rgb2ycbcr.cpp ├── sim_rgb2ycbcr.sh ├── sim_sinus.cpp ├── sim_sinus.sh ├── sim_top.cpp └── sim_top.sh ├── sim2 ├── memory.mpf ├── tb_top.sv └── wave.do └── tools ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── tools.iml └── vcs.xml ├── color.py ├── configure.py ├── debugcom.py ├── debugcom_hil_all.py ├── debugcom_hil_ebu75.py ├── debugcom_hil_parrot.py ├── debugcom_hil_secam.py ├── debugcom_imageviewer.py ├── debugcom_interactive.py ├── defaults.py ├── filterutil.py ├── format.sh ├── framebuffer.py ├── getch.py ├── lint.sh ├── v4l.py ├── vlc_ntsc.sh ├── vlc_pal.sh └── vlc_secam.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | *.bmp filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /gowin/fpga_pong.gprj.user 2 | /tools/__pycache__/ 3 | *.bak 4 | /sim*/work 5 | 6 | /sim*/memory.cr.mti 7 | /sim*/transcript 8 | /sim*/vsim.wlf 9 | /sim/raw_video.png 10 | /tools/secam_carrier.txt 11 | /gowin/testpic_gen.gprj.user 12 | /kicad/circuit/circuit-backups 13 | /kicad/circuit/fp-info-cache 14 | -------------------------------------------------------------------------------- /.rules.verible_lint: -------------------------------------------------------------------------------- 1 | +line-length=length:120 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "assert.h": "c", 4 | "numbers": "cpp", 5 | "cstdio": "cpp", 6 | "cmath": "cpp", 7 | "vector": "cpp", 8 | "cctype": "cpp", 9 | "clocale": "cpp", 10 | "cstdarg": "cpp", 11 | "cstddef": "cpp", 12 | "cstdlib": "cpp", 13 | "cwchar": "cpp", 14 | "array": "cpp", 15 | "atomic": "cpp", 16 | "bit": "cpp", 17 | "*.tcc": "cpp", 18 | "compare": "cpp", 19 | "concepts": "cpp", 20 | "cstdint": "cpp", 21 | "deque": "cpp", 22 | "string": "cpp", 23 | "unordered_map": "cpp", 24 | "exception": "cpp", 25 | "algorithm": "cpp", 26 | "functional": "cpp", 27 | "iterator": "cpp", 28 | "memory": "cpp", 29 | "memory_resource": "cpp", 30 | "numeric": "cpp", 31 | "optional": "cpp", 32 | "random": "cpp", 33 | "string_view": "cpp", 34 | "system_error": "cpp", 35 | "tuple": "cpp", 36 | "type_traits": "cpp", 37 | "utility": "cpp", 38 | "initializer_list": "cpp", 39 | "iosfwd": "cpp", 40 | "limits": "cpp", 41 | "new": "cpp", 42 | "ostream": "cpp", 43 | "stdexcept": "cpp", 44 | "streambuf": "cpp", 45 | "cinttypes": "cpp", 46 | "typeinfo": "cpp" 47 | }, 48 | "editor.formatOnSave": true, 49 | "verilog-formatter.istyle.style": "GNU", 50 | "verilog.formatting.verilogHDL.formatter": "verible-verilog-format", 51 | "verilog.formatting.iStyleVerilogFormatter.style": "GNU", 52 | "verilog.formatting.veribleVerilogFormatter.arguments": "--indentation_spaces 4" 53 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andre Zeps 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 | -------------------------------------------------------------------------------- /doc/circuit_photo.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:20475814d27edeafee5bf07be72d7a8df5f13bca634cf80dede89d40c4db8791 3 | size 2838418 4 | -------------------------------------------------------------------------------- /doc/ebu100.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dbe00f17ec770d63aa14b6b58f17a8113a66091fa94d0021663daeb469fa72df 3 | size 450542 4 | -------------------------------------------------------------------------------- /doc/ebu75.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:354aa0ecc780055fdcedd2aaf5292500c1e453f8e3b8e75f415e7f7d732eeff0 3 | size 458557 4 | -------------------------------------------------------------------------------- /doc/framebuffer.md: -------------------------------------------------------------------------------- 1 | # Framebuffer Device 2 | 3 | ## Thoughts about pixel formats 4 | 5 | * 32 Bit Pixel Format 6 | * 8 Bit Y 7 | * 8 Bit U 8 | * 8 Bit V 9 | * 8 Bit ignored/wasted 10 | 11 | * 16 Bit Pixel format 12 | * 8 Bit Y 13 | * 4 Bit U 14 | * 4 Bit V 15 | 16 | This might cause too much banding. 17 | 18 | * 16 Bit Pixel format 19 | * 6 Bit Y 20 | * 5 Bit U 21 | * 5 Bit V 22 | 23 | But this also causes too much banding. 24 | A 24 bit format might be the best as no space is wasted, bus it is more difficult on the circuit design. 25 | 26 | * 24 Bit Pixel Format 27 | * 8 Bit Y 28 | * 8 Bit U 29 | * 8 Bit V 30 | 31 | ## Register Map 32 | -------------------------------------------------------------------------------- /doc/lessons_learned.md: -------------------------------------------------------------------------------- 1 | # Lessons learned 2 | 3 | ## PSRAM in GOWIN FPGAs is weird 4 | 5 | * There are two PSRAM inside the GW1NR-9 6 | * One PSRAM has an 8 bit data bus 7 | * The PSRAM is addressed in words of 16 bit 8 | * On the side of PSRAM_Memory_Interface_HS_V2, the addresses are in 32 bit words which probably results from its dual channel nature as two PSRAMs are fused into one. 9 | * The smallest burst length is 16 bytes 10 | * With two RAMs this results into a burst of 11 | * 16 bytes * 2 RAMs = 256 bits on the RAM bus 12 | * 64 bits * 4 clocks = 256 bits on the PSRAM memory controller 13 | * The smallest addressable element from user logic view is a 32 bit word 14 | * With 32 bits per pixel the smallest addressable element is a pixel 15 | * A burst consists of 8 pixels 16 | * Because of the wrapped burst nature of the PSRAM, every memory access shall be started aligned to 8 pixels / 16 bytes 17 | * If this is not followed, weird wrap-arounds are visible in the picture 18 | * The framebuffer device should discard the non wanted pixels at the start of the line 19 | 20 | ## Resetting of the QAM phase accumulator on a new frame or field is a bad idea 21 | 22 | I first thought this might improve the pixel stability as dot crawl will no longer occur. 23 | But I was wrong, as this has bad effects on at the least the PAL decoder of the Commodore 1084 as it creates weird flickering in the top half of the screen. 24 | It should be noted that my USB video grabber doesn't care at all. 25 | 26 | ## For some reason, the picture on the Commodore 1084 is darker sometimes 27 | 28 | Invalid pixel data after system reset confuses the automatic gain control. It stays like that and and the video signal must be removed to fix this. 29 | 30 | ## For some reason, the Commodore 1084 is unhappy with 1V p-p signals 31 | 32 | My USB video grabber accepts it, but the 1084 clips white pixels to black. I've reduced 0.3V black and 1V white 33 | to 0.2V black and 0.7V white. I don't know if this is the right solution but it works for the 1084 and the USB video grabber is also happy with that. 34 | 35 | ## Interlacing causes flicker on hard vertical edges 36 | 37 | Vertical blurring is required! Oh god, I hate interlaced modes. Especially on the Workbench of the Amiga. 38 | 39 | ## SECAM is very complicated 40 | 41 | Everything needs to work together to get a nice picture. 42 | PAL and NTSC are transmitting the current state of U and V. 43 | But SECAM transmission also depends on the change of the state as a deemphasis is used on the receiver side. 44 | On the sender side a fitting preemphasis must be performed. This means that there is certain "swing" to the change. One can imagine this as a low pass on the receiver side and the sender side must provide "more" than the intented value to fight against the low pass. 45 | This seems to make SECAM pretty stable as a slight shaky modulation might not affect the result. 46 | If this "swing" is not present, the color looks dull and faded as the expected color will emerge later on. 47 | 48 | But at the same time, there is also something called the HF Preemphasis. The amplitude of the frequency modulated signal must be correct or artefacts will occur during strong modulation. The more deviation the frequency is from a certain "center", the higher the amplitude must be. 49 | 50 | * The amplitude of the color carrier must be correct 51 | * If not, there are artefacts! 52 | * The preemphasis must be correct. 53 | * If not, there are artefacts! 54 | 55 | When I started this I assumed that the line alternation might be the most complex. I was wrong as this part was only a mux. 56 | 57 | Most of this project was spent on refining the SECAM encoder. I don't know if this was really worth it. But I like a good challenge. 58 | -------------------------------------------------------------------------------- /doc/parrot.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:67d8a4d8e8acb7e8339fc4a1a05cb0c1761fcabd2fb77d167630d447fed9e246 3 | size 1983809 4 | -------------------------------------------------------------------------------- /doc/parrot_ntsc.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e24ab37689fe3b1bc7d1f82da38a7ee43b2b1aa7f8a1a50905094cdb4426317d 3 | size 602219 4 | -------------------------------------------------------------------------------- /doc/parrot_pal.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a81fe6ed3dc9bc05d65a6c4cd6a7c5aa1e3fdeb5c38204040aea4274eee2fa4e 3 | size 676540 4 | -------------------------------------------------------------------------------- /doc/parrot_secam.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1318e76a6bd263efce8cfb047d2384578ad8e7842d813421d2c8352fad8259b6 3 | size 723082 4 | -------------------------------------------------------------------------------- /doc/secam_stresstest.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cd61bf932e744a030cf2db5beca1113236edca1f3d68f05615f311f9920e5471 3 | size 13721 4 | -------------------------------------------------------------------------------- /doc/secam_stresstest_result.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:11a979fb1b29348d386dfa6f670044671db00a365d12d14b6a75224cbd7fc72a 3 | size 541970 4 | -------------------------------------------------------------------------------- /gowin/flash.sh: -------------------------------------------------------------------------------- 1 | SCRIPT=$(realpath "$0") 2 | SCRIPTPATH=$(dirname "$SCRIPT") 3 | cd $SCRIPTPATH 4 | 5 | openFPGALoader -f -c ft2232 ./impl/pnr/fpga_pong.fs 6 | -------------------------------------------------------------------------------- /gowin/impl/.gitignore: -------------------------------------------------------------------------------- 1 | /gwsynthesis 2 | /pnr 3 | /temp 4 | 5 | -------------------------------------------------------------------------------- /gowin/impl/project_process_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow_Duplicate_Modules" : false, 3 | "Annotated_Properties_for_Analyst" : true, 4 | "BACKGROUND_PROGRAMMING" : "off", 5 | "CMSER" : false, 6 | "CMSER_CHECKSUM" : false, 7 | "CMSER_MODE" : "auto", 8 | "COMPRESS" : false, 9 | "CPU" : false, 10 | "CRC_CHECK" : true, 11 | "Clock_Conversion" : true, 12 | "Clock_Route_Order" : 0, 13 | "Correct_Hold_Violation" : true, 14 | "DONE" : false, 15 | "DOWNLOAD_SPEED" : "default", 16 | "Default_Enum_Encoding" : "default", 17 | "Disable_Insert_Pad" : false, 18 | "ENABLE_MERGE_MODE" : false, 19 | "ENCRYPTION_KEY" : false, 20 | "ENCRYPTION_KEY_TEXT" : "00000000000000000000000000000000", 21 | "ERROR_DECTION_AND_CORRECTION" : false, 22 | "ERROR_DECTION_ONLY" : false, 23 | "ERROR_INJECTION" : false, 24 | "EXTERNAL_MASTER_CONFIG_CLOCK" : false, 25 | "FORMAT" : "binary", 26 | "FREQUENCY_DIVIDER" : "", 27 | "FSM Compiler" : true, 28 | "Fanout_Guide" : 10000, 29 | "Frequency" : "Auto", 30 | "Generate_Constraint_File_of_Ports" : false, 31 | "Generate_IBIS_File" : false, 32 | "Generate_Plain_Text_Timing_Report" : false, 33 | "Generate_Post_PNR_Simulation_Model_File" : false, 34 | "Generate_Post_Place_File" : false, 35 | "Generate_SDF_File" : false, 36 | "Generate_VHDL_Post_PNR_Simulation_Model_File" : false, 37 | "GwSyn_Loop_Limit" : 2000, 38 | "HOTBOOT" : false, 39 | "I2C" : false, 40 | "I2C_SLAVE_ADDR" : "00", 41 | "Implicit_Initial_Value_Support" : false, 42 | "IncludePath" : [ 43 | 44 | ], 45 | "Incremental_Compile" : "", 46 | "Initialize_Primitives" : false, 47 | "JTAG" : false, 48 | "MODE_IO" : false, 49 | "MSPI" : false, 50 | "MSPI_JUMP" : false, 51 | "MULTIBOOT_ADDRESS_WIDTH" : "24", 52 | "MULTIBOOT_MODE" : "Normal", 53 | "MULTIBOOT_SPI_FLASH_ADDRESS" : "00000000", 54 | "MULTIJUMP_ADDRESS_WIDTH" : "24", 55 | "MULTIJUMP_MODE" : "Normal", 56 | "MULTIJUMP_SPI_FLASH_ADDRESS" : "000000", 57 | "Multi_Boot" : true, 58 | "Multiple_File_Compilation_Unit" : true, 59 | "Number_of_Critical_Paths" : "", 60 | "Number_of_Start/End_Points" : "", 61 | "OUTPUT_BASE_NAME" : "fpga_pong", 62 | "POWER_ON_RESET_MONITOR" : true, 63 | "PRINT_BSRAM_VALUE" : true, 64 | "PROGRAM_DONE_BYPASS" : false, 65 | "Pipelining" : true, 66 | "PlaceInRegToIob" : true, 67 | "PlaceIoRegToIob" : true, 68 | "PlaceOutRegToIob" : true, 69 | "Place_Option" : "0", 70 | "Process_Configuration_Verion" : "1.0", 71 | "Promote_Physical_Constraint_Warning_to_Error" : true, 72 | "Push_Tristates" : true, 73 | "READY" : false, 74 | "RECONFIG_N" : false, 75 | "Ram_RW_Check" : false, 76 | "Replicate_Resources" : false, 77 | "Report_Auto-Placed_Io_Information" : false, 78 | "Resolve_Mixed_Drivers" : false, 79 | "Resource_Sharing" : true, 80 | "Retiming" : false, 81 | "Route_Maxfan" : 23, 82 | "Route_Option" : "0", 83 | "Run_Timing_Driven" : true, 84 | "SECURE_MODE" : false, 85 | "SECURITY_BIT" : true, 86 | "SSPI" : true, 87 | "STOP_CMSER" : false, 88 | "Show_All_Warnings" : false, 89 | "Synthesis On/Off Implemented as Translate On/Off" : false, 90 | "Synthesize_tool" : "GowinSyn", 91 | "TclPre" : "", 92 | "TopModule" : "top_testpic_generator", 93 | "USERCODE" : "default", 94 | "Unused_Pin" : "As_input_tri_stated_with_pull_up", 95 | "Update_Compile_Point_Timing_Data" : false, 96 | "Use_Clock_Period_for_Unconstrainted IO" : false, 97 | "VCCAUX" : 3.3, 98 | "VCCX" : "3.3", 99 | "VHDL_Standard" : "VHDL_Std_1993", 100 | "Verilog_Standard" : "Vlg_Std_Sysv2017", 101 | "WAKE_UP" : "0", 102 | "Write_Vendor_Constraint_File" : true, 103 | "show_all_warnings" : false, 104 | "turn_off_bg" : false 105 | } -------------------------------------------------------------------------------- /gowin/src/gowin_rpll/gowin_rpll.ipc: -------------------------------------------------------------------------------- 1 | [General] 2 | ipc_version=4 3 | file=gowin_rpll 4 | module=Gowin_rPLL 5 | target_device=gw1nr9c-004 6 | type=clock_rpll 7 | version=1.0 8 | 9 | [Config] 10 | CKLOUTD3=false 11 | CLKFB_SOURCE=0 12 | CLKIN_FREQ=27 13 | CLKOUTD=true 14 | CLKOUTP=true 15 | CLKOUT_BYPASS=false 16 | CLKOUT_DIVIDE_DYN=true 17 | CLKOUT_FREQ=96 18 | CLKOUT_TOLERANCE=0 19 | DYNAMIC=false 20 | LANG=0 21 | LOCK_EN=true 22 | MODE_GENERAL=true 23 | PLL_PWD=false 24 | RESET_PLL=false 25 | CLKOUTP_BYPASS=false 26 | CLKOUTP_DUTY_CYCLE=6 27 | CLKOUTP_PHASE=4 28 | CLKOUTD_BYPASS=false 29 | CLKOUTD_FREQ=24 30 | CLKOUTD_SOURCE_CLKOUT=true 31 | CLKOUTD_TOLERANCE=0 32 | -------------------------------------------------------------------------------- /gowin/src/gowin_rpll/gowin_rpll.mod: -------------------------------------------------------------------------------- 1 | -series GW1NR 2 | -device GW1NR-9 3 | -device_version C 4 | -package QFN88P 5 | -part_number GW1NR-LV9QN88PC6/I5 6 | 7 | 8 | -mod_name Gowin_rPLL 9 | -file_name gowin_rpll 10 | -path /home/andre/GIT/fpga_pong/gowin/src/gowin_rpll/ 11 | -type PLL 12 | -rPll true 13 | -file_type vlg 14 | -dev_type GW1NR-9C 15 | -dyn_idiv_sel false 16 | -idiv_sel 9 17 | -dyn_fbdiv_sel false 18 | -fbdiv_sel 32 19 | -dyn_odiv_sel false 20 | -odiv_sel 8 21 | -dyn_sdiv_sel 4 22 | -dyn_da_en false 23 | -rst_sig false 24 | -rst_sig_p false 25 | -fclkin 27 26 | -clkfb_sel 0 27 | -en_lock true 28 | -clkout_bypass false 29 | -en_clkoutp true 30 | -clkoutp_bypass false 31 | -psda_sel 4 32 | -dutyda_sel 8 33 | -en_clkoutd true 34 | -clkoutd_bypass false 35 | -clkoutd_src CLKOUT 36 | -en_clkoutd3 false -------------------------------------------------------------------------------- /gowin/src/gowin_rpll/gowin_rpll.v: -------------------------------------------------------------------------------- 1 | //Copyright (C)2014-2023 Gowin Semiconductor Corporation. 2 | //All rights reserved. 3 | //File Title: IP file 4 | //GOWIN Version: V1.9.9 Beta-6 5 | //Part Number: GW1NR-LV9QN88PC6/I5 6 | //Device: GW1NR-9 7 | //Device Version: C 8 | //Created Time: Thu Nov 16 19:10:40 2023 9 | 10 | module Gowin_rPLL (clkout, lock, clkoutp, clkoutd, clkin); 11 | 12 | output clkout; 13 | output lock; 14 | output clkoutp; 15 | output clkoutd; 16 | input clkin; 17 | 18 | wire clkoutd3_o; 19 | wire gw_vcc; 20 | wire gw_gnd; 21 | 22 | assign gw_vcc = 1'b1; 23 | assign gw_gnd = 1'b0; 24 | 25 | rPLL rpll_inst ( 26 | .CLKOUT(clkout), 27 | .LOCK(lock), 28 | .CLKOUTP(clkoutp), 29 | .CLKOUTD(clkoutd), 30 | .CLKOUTD3(clkoutd3_o), 31 | .RESET(gw_gnd), 32 | .RESET_P(gw_gnd), 33 | .CLKIN(clkin), 34 | .CLKFB(gw_gnd), 35 | .FBDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 36 | .IDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 37 | .ODSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 38 | .PSDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 39 | .DUTYDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), 40 | .FDLY({gw_vcc,gw_vcc,gw_vcc,gw_vcc}) 41 | ); 42 | 43 | defparam rpll_inst.FCLKIN = "27"; 44 | defparam rpll_inst.DYN_IDIV_SEL = "false"; 45 | defparam rpll_inst.IDIV_SEL = 8; 46 | defparam rpll_inst.DYN_FBDIV_SEL = "false"; 47 | defparam rpll_inst.FBDIV_SEL = 31; 48 | defparam rpll_inst.DYN_ODIV_SEL = "false"; 49 | defparam rpll_inst.ODIV_SEL = 8; 50 | defparam rpll_inst.PSDA_SEL = "0100"; 51 | defparam rpll_inst.DYN_DA_EN = "false"; 52 | defparam rpll_inst.DUTYDA_SEL = "1000"; 53 | defparam rpll_inst.CLKOUT_FT_DIR = 1'b1; 54 | defparam rpll_inst.CLKOUTP_FT_DIR = 1'b1; 55 | defparam rpll_inst.CLKOUT_DLY_STEP = 0; 56 | defparam rpll_inst.CLKOUTP_DLY_STEP = 0; 57 | defparam rpll_inst.CLKFB_SEL = "internal"; 58 | defparam rpll_inst.CLKOUT_BYPASS = "false"; 59 | defparam rpll_inst.CLKOUTP_BYPASS = "false"; 60 | defparam rpll_inst.CLKOUTD_BYPASS = "false"; 61 | defparam rpll_inst.DYN_SDIV_SEL = 4; 62 | defparam rpll_inst.CLKOUTD_SRC = "CLKOUT"; 63 | defparam rpll_inst.CLKOUTD3_SRC = "CLKOUT"; 64 | defparam rpll_inst.DEVICE = "GW1NR-9C"; 65 | 66 | endmodule //Gowin_rPLL 67 | -------------------------------------------------------------------------------- /gowin/src/gowin_rpll/gowin_rpll_tmp.v: -------------------------------------------------------------------------------- 1 | //Copyright (C)2014-2023 Gowin Semiconductor Corporation. 2 | //All rights reserved. 3 | //File Title: Template file for instantiation 4 | //GOWIN Version: V1.9.9 Beta-6 5 | //Part Number: GW1NR-LV9QN88PC6/I5 6 | //Device: GW1NR-9 7 | //Device Version: C 8 | //Created Time: Thu Nov 16 19:10:40 2023 9 | 10 | //Change the instance name and port connections to the signal names 11 | //--------Copy here to design-------- 12 | 13 | Gowin_rPLL your_instance_name( 14 | .clkout(clkout_o), //output clkout 15 | .lock(lock_o), //output lock 16 | .clkoutp(clkoutp_o), //output clkoutp 17 | .clkoutd(clkoutd_o), //output clkoutd 18 | .clkin(clkin_i) //input clkin 19 | ); 20 | 21 | //--------Copy end------------------- 22 | -------------------------------------------------------------------------------- /gowin/src/psram_memory_interface_hs_v2/psram_memory_interface_hs_v2.ipc: -------------------------------------------------------------------------------- 1 | [General] 2 | ipc_version=4 3 | file=psram_memory_interface_hs_v2 4 | module=PSRAM_Memory_Interface_HS_V2_Top 5 | target_device=gw1nr9c-004 6 | type=psram_hs_v2 7 | version=2.0 8 | 9 | [Config] 10 | ADDR_WIDTH=21 11 | BURST_MODE=16 12 | DEEP_POWER_DOWN=OFF 13 | DISABLE_IO=true 14 | DQ_WIDTH=16 15 | DRIVE_STRENGTH=50 16 | HYBRID_SLEEP_MODE=OFF 17 | INITIAL_LATENCY=6 18 | LANG=0 19 | MEMORY_CLK=96 20 | MEMORY_TYPE=W955D8MBYA 21 | PASR=full 22 | PSRAM_WIDTH=8 23 | REFRESH_RATE=normal 24 | SHIFT_DELAY=216 25 | SIMULATION=false 26 | Synthesis_tool=GowinSynthesis 27 | -------------------------------------------------------------------------------- /gowin/src/psram_memory_interface_hs_v2/psram_memory_interface_hs_v2_tmp.v: -------------------------------------------------------------------------------- 1 | //Copyright (C)2014-2023 Gowin Semiconductor Corporation. 2 | //All rights reserved. 3 | //File Title: Template file for instantiation 4 | //GOWIN Version: V1.9.9 Beta-6 5 | //Part Number: GW1NR-LV9QN88PC6/I5 6 | //Device: GW1NR-9 7 | //Device Version: C 8 | //Created Time: Thu Nov 16 19:13:05 2023 9 | 10 | //Change the instance name and port connections to the signal names 11 | //--------Copy here to design-------- 12 | 13 | PSRAM_Memory_Interface_HS_V2_Top your_instance_name( 14 | .clk_d(clk_d_i), //input clk_d 15 | .memory_clk(memory_clk_i), //input memory_clk 16 | .memory_clk_p(memory_clk_p_i), //input memory_clk_p 17 | .pll_lock(pll_lock_i), //input pll_lock 18 | .rst_n(rst_n_i), //input rst_n 19 | .O_psram_ck(O_psram_ck_o), //output [1:0] O_psram_ck 20 | .O_psram_ck_n(O_psram_ck_n_o), //output [1:0] O_psram_ck_n 21 | .IO_psram_dq(IO_psram_dq_io), //inout [15:0] IO_psram_dq 22 | .IO_psram_rwds(IO_psram_rwds_io), //inout [1:0] IO_psram_rwds 23 | .O_psram_cs_n(O_psram_cs_n_o), //output [1:0] O_psram_cs_n 24 | .O_psram_reset_n(O_psram_reset_n_o), //output [1:0] O_psram_reset_n 25 | .wr_data(wr_data_i), //input [63:0] wr_data 26 | .rd_data(rd_data_o), //output [63:0] rd_data 27 | .rd_data_valid(rd_data_valid_o), //output rd_data_valid 28 | .addr(addr_i), //input [20:0] addr 29 | .cmd(cmd_i), //input cmd 30 | .cmd_en(cmd_en_i), //input cmd_en 31 | .init_calib(init_calib_o), //output init_calib 32 | .clk_out(clk_out_o), //output clk_out 33 | .data_mask(data_mask_i) //input [7:0] data_mask 34 | ); 35 | 36 | //--------Copy end------------------- 37 | -------------------------------------------------------------------------------- /gowin/src/testpic_gen.cst: -------------------------------------------------------------------------------- 1 | //Copyright (C)2014-2023 Gowin Semiconductor Corporation. 2 | //All rights reserved. 3 | //File Title: Physical Constraints file 4 | //GOWIN Version: 1.9.9 Beta-4 Education 5 | //Part Number: GW1NR-LV9QN88PC6/I5 6 | //Device: GW1NR-9 7 | //Device Version: C 8 | //Created Time: Sat 10 28 01:03:42 2023 9 | 10 | IO_LOC "video_extra[7]" 40; 11 | IO_PORT "video_extra[7]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=16 BANK_VCCIO=3.3; 12 | IO_LOC "video[7]" 35; 13 | IO_PORT "video[7]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 14 | IO_LOC "video_extra[6]" 41; 15 | IO_PORT "video_extra[6]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=16 BANK_VCCIO=3.3; 16 | IO_LOC "video[6]" 42; 17 | IO_PORT "video[6]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 18 | IO_LOC "video[5]" 51; 19 | IO_PORT "video[5]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 20 | IO_LOC "video[4]" 53; 21 | IO_PORT "video[4]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=4 BANK_VCCIO=3.3; 22 | IO_LOC "video[3]" 54; 23 | IO_PORT "video[3]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=4 BANK_VCCIO=3.3; 24 | IO_LOC "video[2]" 55; 25 | IO_PORT "video[2]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 26 | IO_LOC "video[1]" 56; 27 | IO_PORT "video[1]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 28 | IO_LOC "video[0]" 57; 29 | IO_PORT "video[0]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; 30 | 31 | IO_LOC "clk27" 52; 32 | IO_PORT "clk27" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3; 33 | 34 | IO_LOC "uart_tx" 17; 35 | IO_PORT "uart_tx" IO_TYPE=LVCMOS33 PULL_MODE=UP; 36 | IO_LOC "uart_rx" 18; 37 | IO_PORT "uart_rx" IO_TYPE=LVCMOS33 PULL_MODE=UP; 38 | 39 | IO_LOC "switch1" 3; 40 | IO_PORT "switch1" IO_TYPE=LVCMOS33 PULL_MODE=UP; 41 | 42 | IO_LOC "sys_resetn" 4; 43 | IO_PORT "sys_resetn" PULL_MODE=UP; 44 | 45 | // LED 46 | IO_LOC "led[5]" 16; 47 | IO_PORT "led[5]" PULL_MODE=UP DRIVE=8; 48 | IO_LOC "led[4]" 15; 49 | IO_PORT "led[4]" PULL_MODE=UP DRIVE=8; 50 | IO_LOC "led[3]" 14; 51 | IO_PORT "led[3]" PULL_MODE=UP DRIVE=8; 52 | IO_LOC "led[2]" 13; 53 | IO_PORT "led[2]" PULL_MODE=UP DRIVE=8; 54 | IO_LOC "led[1]" 11; 55 | IO_PORT "led[1]" PULL_MODE=UP DRIVE=8; 56 | IO_LOC "led[0]" 10; 57 | IO_PORT "led[0]" PULL_MODE=UP DRIVE=8; -------------------------------------------------------------------------------- /gowin/src/testpic_gen.sdc: -------------------------------------------------------------------------------- 1 | # 27 MHz 2 | create_clock -name clk27 -period 37.037 [get_ports {clk27}] 3 | 4 | -------------------------------------------------------------------------------- /gowin/testpic_gen.gprj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 5 6 | gw1nr9c-004 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /gowin/upload.sh: -------------------------------------------------------------------------------- 1 | SCRIPT=$(realpath "$0") 2 | SCRIPTPATH=$(dirname "$SCRIPT") 3 | cd $SCRIPTPATH 4 | 5 | openFPGALoader -c ft2232 ./impl/pnr/fpga_pong.fs 6 | -------------------------------------------------------------------------------- /kicad/circuit/circuit.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": false, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 39, 65 | 40 66 | ], 67 | "visible_layers": "fffffff_ffffffff", 68 | "zone_display_mode": 0 69 | }, 70 | "meta": { 71 | "filename": "circuit.kicad_prl", 72 | "version": 3 73 | }, 74 | "project": { 75 | "files": [] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /kicad/circuit/circuit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slamy/fpga-composite-video/293376c29d4249cfe1ec4928adee0387d9ea00b3/kicad/circuit/circuit.pdf -------------------------------------------------------------------------------- /rtl/RGB2YCbCr.sv: -------------------------------------------------------------------------------- 1 | `include "common.svh" 2 | 3 | import common::*; 4 | 5 | /* 6 | * Converts RGB values into YCbCr 7 | */ 8 | module RGB2YCbCr ( 9 | input clk, 10 | input rgb_t in, 11 | output ycbcr_t out 12 | ) /* synthesis syn_dspstyle = "logic" */; 13 | 14 | bit [7:0] Y_temp1; 15 | bit [7:0] Y_temp2; 16 | bit [7:0] Y_temp3; 17 | 18 | bit signed [7:0] Cb_temp1; 19 | bit signed [7:0] Cb_temp2; 20 | bit signed [7:0] Cb_temp3; 21 | 22 | bit signed [7:0] Cr_temp1; 23 | bit signed [7:0] Cr_temp2; 24 | bit signed [7:0] Cr_temp3; 25 | always_ff @(posedge clk) begin 26 | 27 | // Translation matrix from https://en.wikipedia.org/wiki/YCbCr 28 | // Approximate 8-bit matrices for BT.601 as full swing 29 | Y_temp1 <= (77 * in.r) >> 8; 30 | Y_temp2 <= (150 * in.g) >> 8; 31 | Y_temp3 <= (29 * in.b) >> 8; 32 | 33 | Cb_temp1 <= (43 * in.r) >> 8; 34 | Cb_temp2 <= (84 * in.g) >> 8; 35 | 36 | // The GowinSynthesis tool is not very smart as it seems 37 | // 127 * B was replaced by 128 * B - B for optimization. 38 | // This replaces 6 numbers to add together with only 2. 39 | Cb_temp3 <= (128 * in.b - in.b) >> 8; 40 | 41 | Cr_temp1 <= (128 * in.r - in.r) >> 8; 42 | Cr_temp2 <= (106 * in.g) >> 8; 43 | Cr_temp3 <= (21 * in.b) >> 8; 44 | 45 | out.y <= Y_temp1 + Y_temp2 + Y_temp3; 46 | out.cb <= -Cb_temp1 - Cb_temp2 + Cb_temp3; 47 | out.cr <= Cr_temp1 - Cr_temp2 - Cr_temp3; 48 | end 49 | endmodule : RGB2YCbCr 50 | -------------------------------------------------------------------------------- /rtl/burst_bus_arbiter.sv: -------------------------------------------------------------------------------- 1 | module burst_bus_arbiter ( 2 | burst_bus_if.master mem, 3 | burst_bus_if.slave m1, 4 | burst_bus_if.slave m2 5 | ); 6 | 7 | bit m1_active = 0; 8 | bit m2_active = 0; 9 | 10 | always_ff @(posedge mem.clk) begin 11 | 12 | if (mem.ready) begin 13 | if (m1.cmd_en) begin 14 | m1_active <= 1; 15 | m2_active <= 0; 16 | end else if (m2.cmd_en) begin 17 | m2_active <= 1; 18 | m1_active <= 0; 19 | end 20 | end 21 | end 22 | 23 | always_comb begin 24 | m1.rd_data_valid = 0; 25 | m2.rd_data_valid = 0; 26 | mem.cmd_en = 0; 27 | 28 | m1.rd_data = mem.rd_data; 29 | m2.rd_data = mem.rd_data; 30 | 31 | mem.cmd = m2.cmd; 32 | mem.wr_data = m2.wr_data; 33 | mem.addr = m2.addr; 34 | mem.data_mask = m2.data_mask; 35 | 36 | if (m1_active) begin 37 | m1.rd_data_valid = mem.rd_data_valid; 38 | 39 | mem.cmd = m1.cmd; 40 | mem.wr_data = m1.wr_data; 41 | mem.addr = m1.addr; 42 | mem.data_mask = m1.data_mask; 43 | 44 | end else if (m2_active) begin 45 | m2.rd_data_valid = mem.rd_data_valid; 46 | end 47 | 48 | 49 | if (mem.ready) begin 50 | if (m1.cmd_en) begin 51 | // Master 1 has a higher priority 52 | m1.ready = 1; 53 | m2.ready = 0; 54 | mem.cmd_en = 1; 55 | 56 | mem.cmd = m1.cmd; 57 | mem.wr_data = m1.wr_data; 58 | mem.addr = m1.addr; 59 | mem.data_mask = m1.data_mask; 60 | 61 | end else if (m2.cmd_en) begin 62 | // Master 2 has the lower priority 63 | m2.ready = 1; 64 | m1.ready = 0; 65 | mem.cmd_en = 1; 66 | 67 | mem.cmd = m2.cmd; 68 | mem.wr_data = m2.wr_data; 69 | mem.addr = m2.addr; 70 | mem.data_mask = m2.data_mask; 71 | end else begin 72 | m1.ready = mem.ready; 73 | m2.ready = mem.ready; 74 | end 75 | end else begin 76 | // Memory is not ready? Forward that to both 77 | m1.ready = 0; 78 | m2.ready = 0; 79 | end 80 | end 81 | 82 | endmodule 83 | -------------------------------------------------------------------------------- /rtl/burst_writer.sv: -------------------------------------------------------------------------------- 1 | /* 2 | * Collects a whole burst of octets to write into memory. 3 | * Ensures efficient usage of memory controller bandwidth during larger memory writes. 4 | */ 5 | module burst_writer ( 6 | input reset, 7 | input [7:0] data, 8 | input strobe, 9 | burst_bus_if.master mem 10 | ); 11 | 12 | bit [63:0] burst_data[4]; 13 | bit [4:0] burst_data_write_address = 0; 14 | bit [1:0] burst_data_read_adr_d; 15 | bit [1:0] burst_data_read_adr_q = 0; 16 | 17 | bit cmd_en_d; 18 | bit increment_burst_addr; 19 | 20 | always_ff @(posedge mem.clk) begin 21 | if (reset) begin 22 | mem.addr <= 21'h500; // TODO make configurable 23 | burst_data_write_address <= 0; 24 | burst_data_read_adr_q <= 0; 25 | end else begin 26 | if (strobe) begin 27 | case (burst_data_write_address[2:0]) 28 | 0: burst_data[burst_data_write_address[4:3]][63:56] <= data; 29 | 1: burst_data[burst_data_write_address[4:3]][55:48] <= data; 30 | 2: burst_data[burst_data_write_address[4:3]][47:40] <= data; 31 | 3: burst_data[burst_data_write_address[4:3]][39:32] <= data; 32 | 4: burst_data[burst_data_write_address[4:3]][31:24] <= data; 33 | 5: burst_data[burst_data_write_address[4:3]][23:16] <= data; 34 | 6: burst_data[burst_data_write_address[4:3]][15:8] <= data; 35 | 7: burst_data[burst_data_write_address[4:3]][7:0] <= data; 36 | default: ; 37 | endcase 38 | burst_data_write_address <= burst_data_write_address + 1; 39 | end 40 | 41 | mem.wr_data <= burst_data[burst_data_read_adr_d]; 42 | mem.cmd_en <= cmd_en_d; 43 | burst_data_read_adr_q <= burst_data_read_adr_d; 44 | 45 | if (increment_burst_addr) mem.addr <= mem.addr + 8; 46 | end 47 | end 48 | 49 | always_comb begin 50 | mem.cmd = 1; 51 | cmd_en_d = 0; 52 | mem.data_mask = 0; 53 | increment_burst_addr = 0; 54 | burst_data_read_adr_d = burst_data_read_adr_q; 55 | 56 | // Received everything? Then go and write the burst 57 | if (strobe && burst_data_write_address == 5'b11111) begin 58 | cmd_en_d = 1; 59 | end 60 | 61 | // Hold cmd_en until ready 62 | if (mem.cmd_en && !mem.ready) cmd_en_d = 1; 63 | 64 | // If ready, we are writing the first word this cycle 65 | if (mem.cmd_en && mem.ready) begin 66 | burst_data_read_adr_d = burst_data_read_adr_q + 1; 67 | cmd_en_d = 0; 68 | end 69 | 70 | // If started, continue 71 | if (burst_data_read_adr_q != 0) begin 72 | burst_data_read_adr_d = burst_data_read_adr_q + 1; 73 | end 74 | 75 | // just provided the last word to write? Increment burst address 76 | if (burst_data_read_adr_q == 3) increment_burst_addr = 1; 77 | 78 | end 79 | endmodule 80 | -------------------------------------------------------------------------------- /rtl/coefficients.svh: -------------------------------------------------------------------------------- 1 | `define PAL_CHROMA_B0 5 2 | `define PAL_CHROMA_B1 0 3 | `define PAL_CHROMA_B2 -5 4 | `define PAL_CHROMA_A0 32 5 | `define PAL_CHROMA_A1 -45 6 | `define PAL_CHROMA_A2 22 7 | `define PAL_CHROMA_B_AFTER_DOT 5 8 | `define PAL_CHROMA_A_AFTER_DOT 5 9 | 10 | `define NTSC_CHROMA_B0 5 11 | `define NTSC_CHROMA_B1 0 12 | `define NTSC_CHROMA_B2 -5 13 | `define NTSC_CHROMA_A0 32 14 | `define NTSC_CHROMA_A1 -48 15 | `define NTSC_CHROMA_A2 22 16 | `define NTSC_CHROMA_B_AFTER_DOT 5 17 | `define NTSC_CHROMA_A_AFTER_DOT 5 18 | 19 | `define PAL_LUMA_LOWPASS_B0 20 20 | `define PAL_LUMA_LOWPASS_B1 41 21 | `define PAL_LUMA_LOWPASS_B2 20 22 | `define PAL_LUMA_LOWPASS_A0 1024 23 | `define PAL_LUMA_LOWPASS_A1 -1530 24 | `define PAL_LUMA_LOWPASS_A2 587 25 | `define PAL_LUMA_LOWPASS_B_AFTER_DOT 10 26 | `define PAL_LUMA_LOWPASS_A_AFTER_DOT 10 27 | 28 | `define SECAM_AMPLITUDE_LOWPASS_B0 65 29 | `define SECAM_AMPLITUDE_LOWPASS_B1 65 30 | `define SECAM_AMPLITUDE_LOWPASS_B2 0 31 | `define SECAM_AMPLITUDE_LOWPASS_A0 256 32 | `define SECAM_AMPLITUDE_LOWPASS_A1 -240 33 | `define SECAM_AMPLITUDE_LOWPASS_A2 0 34 | `define SECAM_AMPLITUDE_LOWPASS_B_AFTER_DOT 11 35 | `define SECAM_AMPLITUDE_LOWPASS_A_AFTER_DOT 8 36 | 37 | `define SECAM_CHROMA_LOWPASS_B0 217 38 | `define SECAM_CHROMA_LOWPASS_B1 217 39 | `define SECAM_CHROMA_LOWPASS_B2 0 40 | `define SECAM_CHROMA_LOWPASS_A0 256 41 | `define SECAM_CHROMA_LOWPASS_A1 -202 42 | `define SECAM_CHROMA_LOWPASS_A2 0 43 | `define SECAM_CHROMA_LOWPASS_B_AFTER_DOT 11 44 | `define SECAM_CHROMA_LOWPASS_A_AFTER_DOT 8 45 | 46 | `define SECAM_PREEMPHASIS_B0 13 47 | `define SECAM_PREEMPHASIS_B1 13 48 | `define SECAM_PREEMPHASIS_B2 0 49 | `define SECAM_PREEMPHASIS_A0 2048 50 | `define SECAM_PREEMPHASIS_A1 -2021 51 | `define SECAM_PREEMPHASIS_A2 0 52 | `define SECAM_PREEMPHASIS_B_AFTER_DOT 11 53 | `define SECAM_PREEMPHASIS_A_AFTER_DOT 11 54 | 55 | `define CLK_PERIOD_USEC 0.020833333333333332 // .8 56 | 57 | `define SECAM_CHROMA_DB_DDS_INCREMENT 51'd199378108503381 58 | `define SECAM_CHROMA_DR_DDS_INCREMENT 51'd206708655146849 59 | `define PAL_CHROMA_DDS_INCREMENT 51'd207992122400030 60 | `define NTSC_CHROMA_DDS_INCREMENT 51'd167925390918291 61 | 62 | `define PAL_BURST_U -8 63 | `define PAL_BURST_V 8 64 | `define NTSC_BURST_U -14 65 | `define NTSC_BURST_V 4 66 | 67 | -------------------------------------------------------------------------------- /rtl/common.svh: -------------------------------------------------------------------------------- 1 | 2 | `ifndef COMMON_H 3 | `define COMMON_H 4 | 5 | package common; 6 | typedef enum bit { 7 | MODE_50HZ, 8 | MODE_60HZ 9 | } vertical_frequency_e; 10 | 11 | typedef enum bit [1:0] { 12 | PAL, 13 | NTSC, 14 | SECAM 15 | } video_standard_e; 16 | 17 | 18 | // Digital RGB signal with 8 bit depth 19 | typedef struct { 20 | bit [7:0] r; 21 | bit [7:0] g; 22 | bit [7:0] b; 23 | } rgb_t; 24 | 25 | // YUV signals according to PAL analog scaling 26 | typedef struct { 27 | bit [7:0] y; 28 | bit signed [7:0] u; 29 | bit signed [7:0] v; 30 | } yuv_t; 31 | 32 | // YCbCr but as signed 33 | // Usually the "neutral" of Cb and Cr is 128 but here it is 0 34 | // with a swing of -127 to 127 35 | typedef struct { 36 | bit [7:0] y; 37 | bit signed [7:0] cb; 38 | bit signed [7:0] cr; 39 | } ycbcr_t; 40 | 41 | 42 | 43 | endpackage 44 | 45 | // Burst based memory port suitable for PSRAM_HS_V2 IP core 46 | interface burst_bus_if ( 47 | input clk 48 | ); 49 | bit [63:0] wr_data; 50 | bit [63:0] rd_data; 51 | bit cmd; 52 | bit cmd_en; 53 | bit [20:0] addr; 54 | bit ready; 55 | bit [ 7:0] data_mask; 56 | bit rd_data_valid; 57 | 58 | modport master( 59 | input clk, rd_data, ready, rd_data_valid, 60 | output addr, cmd, cmd_en, wr_data, data_mask 61 | ); 62 | 63 | modport slave( 64 | input clk, addr, cmd, cmd_en, wr_data, data_mask, 65 | output rd_data, ready, rd_data_valid 66 | ); 67 | 68 | endinterface 69 | 70 | 71 | // 16 Bit, single cycle bus 72 | interface debug_bus_if ( 73 | input clk 74 | ); 75 | bit [15:0] addr; 76 | bit write_enable; 77 | bit read_enable; 78 | bit read_data_valid; 79 | bit [ 7:0] read_data; 80 | bit [ 7:0] write_data; 81 | bit ready; 82 | 83 | modport master( 84 | input clk, read_data_valid, read_data, ready, 85 | output addr, write_enable, read_enable, write_data 86 | ); 87 | 88 | modport slave( 89 | input clk, addr, write_enable, read_enable, write_data, 90 | output read_data_valid, read_data, ready 91 | ); 92 | endinterface 93 | 94 | 95 | `endif 96 | -------------------------------------------------------------------------------- /rtl/composite_video_encoder.sv: -------------------------------------------------------------------------------- 1 | `include "configuration.svh" 2 | `include "common.svh" 3 | `include "coefficients.svh" 4 | 5 | import common::*; 6 | 7 | /* 8 | * Composite Video Baseband Signal Encoder 9 | * Produces PAL, NTSC and SECAM encoded analog video. 10 | */ 11 | module composite_video_encoder ( 12 | input clk, // Clock signal as configured in python script 13 | input sync, // Analog vidoe sync signal 14 | input newframe, // Flag which marks start of frame 15 | input newline, // Flag which marks start of scanline 16 | input qam_startburst, // Starts PAL/NTSC burst 17 | input secam_enabled, // Activates SECAM carrier 18 | input video_standard_e video_standard, // PAL, NTSC or SECAM 19 | input ycbcr_t in, // Digital video input 20 | output bit [7:0] video, // Analog video output 21 | output bit video_overflow, // If 1, then mixing error occured 22 | debug_bus_if.slave dbus // Debug interface 23 | ); 24 | 25 | bit [5:0] debug_burst_u = `NTSC_BURST_U; // TODO remove 26 | bit [5:0] debug_burst_v = `NTSC_BURST_V; // TODO remove 27 | bit [7:0] secam_debug_db_swing = `CONFIG_SECAM_DB_SWING; 28 | bit [6:0] secam_debug_dr_swing = `CONFIG_SECAM_DR_SWING; 29 | bit [4:0] secam_debug_carrier_delay = 0; 30 | bit chroma_lowpass_enable = 0; // TODO remove 31 | bit chroma_bandpass_enable = 1; // TODO remove 32 | bit chroma_enable = 1; 33 | 34 | ycbcr_t in_q; 35 | 36 | bit [7:0] luma_black_level = 52; 37 | /* TODO for GOWIN support 38 | * Something here is wrong. It is required to set the ram style 39 | * to "registers" to allow initialization. Otherwise it is all zeroes. 40 | */ 41 | bit [7:0] y_scaler_mem[4] /* synthesis syn_ramstyle = "registers" */ = { 42 | `CONFIG_PAL_Y_SCALER, `CONFIG_NTSC_Y_SCALER, `CONFIG_SECAM_Y_SCALER, 0 43 | }; 44 | bit signed [7:0] u_scaler_mem[4] /* synthesis syn_ramstyle = "registers" */ = { 45 | `CONFIG_PAL_U_SCALER, `CONFIG_NTSC_U_SCALER, `CONFIG_SECAM_U_SCALER, 0 46 | }; 47 | bit signed [7:0] v_scaler_mem[4] /* synthesis syn_ramstyle = "registers" */ = { 48 | `CONFIG_PAL_V_SCALER, `CONFIG_NTSC_V_SCALER, `CONFIG_SECAM_V_SCALER, 0 49 | }; 50 | 51 | yuv_t scaler; 52 | yuv_t scaled; 53 | 54 | // Handle debug bus for scaler configuration configuratuion 55 | always_ff @(posedge clk) begin 56 | if (dbus.addr[15:8] == 8'h02 && dbus.write_enable) begin 57 | case (dbus.addr[3:2]) 58 | 0: y_scaler_mem[dbus.addr[1:0]] <= dbus.write_data; 59 | 1: u_scaler_mem[dbus.addr[1:0]] <= dbus.write_data; 60 | 2: v_scaler_mem[dbus.addr[1:0]] <= dbus.write_data; 61 | default: ; 62 | endcase 63 | end 64 | end 65 | 66 | always_ff @(posedge clk) begin 67 | // Perform the readout using a seperate step to reduce 68 | // propagation delay 69 | scaler.y <= y_scaler_mem[video_standard]; 70 | scaler.u <= u_scaler_mem[video_standard]; 71 | scaler.v <= v_scaler_mem[video_standard]; 72 | 73 | // Apply scaling to convert YCbCr into YUV using configurable scalers 74 | scaled.y <= 8'((16'(in_q.y) * 16'(scaler.y)) >> 8); 75 | scaled.u <= 8'((16'(in_q.cb) * 16'(scaler.u)) >>> 8); 76 | scaled.v <= 8'((16'(in_q.cr) * 16'(scaler.v)) >>> 8); 77 | end 78 | 79 | // The filters used on chroma and luma might cause both signals 80 | // to get out of phase. This delay line will ensure that both do again 81 | // align in the final sum 82 | bit [4:0] luma_delay_duration = 0; 83 | bit [4:0] yuv_u_delay_duration = 0; 84 | bit [4:0] yuv_v_delay_duration = 0; 85 | 86 | yuv_t delayed; 87 | 88 | delayfifo #(8) dfy ( 89 | .clk, 90 | .in(scaled.y), 91 | .latency(luma_delay_duration), 92 | .out(delayed.y) 93 | ); 94 | 95 | delayfifo #(8) dfu ( 96 | .clk, 97 | .in(scaled.u), 98 | .latency(yuv_u_delay_duration), 99 | .out(delayed.u) 100 | ); 101 | 102 | delayfifo #(8) dfv ( 103 | .clk, 104 | .in(scaled.v), 105 | .latency(yuv_v_delay_duration), 106 | .out(delayed.v) 107 | ); 108 | 109 | bit [7:0] luma_filtered; 110 | 111 | // The luma signal is not allowed to have higher frequencies as 112 | // it could reach the color carrier and cause rainbow artefacts. 113 | // These are filtered out using this low pass. 114 | filter_pal_luma lumafilter0 ( 115 | .clk(clk), 116 | .in (delayed.y), 117 | .out(luma_filtered) 118 | ); 119 | 120 | bit even_line = 0; 121 | always_ff @(posedge clk) begin 122 | if (newline) even_line <= !even_line; 123 | end 124 | 125 | bit signed [7:0] pal_ntsc_chroma; 126 | `ifdef CONFIG_PAL_NTSC_ENABLED 127 | wire pal_mode = (video_standard == PAL); 128 | pal_ntsc_encoder pal_ntsc ( 129 | .clk, 130 | .even_line, 131 | .newframe, 132 | .newline, 133 | .pal_mode, 134 | .chroma_lowpass_enable, 135 | .chroma_bandpass_enable, 136 | .yuv_u(delayed.u), 137 | .yuv_v(delayed.v), 138 | .startburst(qam_startburst), 139 | .chroma(pal_ntsc_chroma), 140 | .debug_burst_u, 141 | .debug_burst_v 142 | ); 143 | `endif 144 | 145 | bit signed [7:0] secam_chroma; 146 | `ifdef CONFIG_SECAM_ENABLED 147 | secam_encoder secam ( 148 | .clk, 149 | .even_line, 150 | .yuv_u(delayed.u), 151 | .yuv_v(delayed.v), 152 | .debug_db_swing(secam_debug_db_swing), 153 | .debug_dr_swing(secam_debug_dr_swing), 154 | .carrier_period_delay(secam_debug_carrier_delay), 155 | .chroma_lowpass_enable, 156 | .enabled(secam_enabled), 157 | .newframe(newframe), 158 | .luma_filtered, 159 | .chroma(secam_chroma) 160 | ); 161 | `endif 162 | 163 | // The video output of this module is only 8 bit, but 164 | // we perform the calculations here on 9 bit to catch 165 | // integer overflows. 166 | bit [8:0] video_d; 167 | bit [8:0] video_q; 168 | 169 | always_ff @(posedge clk) begin 170 | video_q <= video_d; 171 | in_q <= in; 172 | 173 | if (newframe) video_overflow <= 0; 174 | else if (video_d[8]) video_overflow <= 1; 175 | 176 | if (video_q[8]) video <= 0; 177 | else video <= video_q[7:0]; 178 | end 179 | 180 | // Add everything together 181 | always_comb begin 182 | video_d = 0; // Sync case with GND level is the default 183 | 184 | if (!sync) begin 185 | // Sync is not active? Lift up to black level. 186 | 187 | if (!chroma_enable) begin 188 | // Chroma is disabled. Add luma without low pass filter 189 | video_d = luma_black_level + delayed.y; 190 | end else begin 191 | // Chroma is enable. Add luma with low pass filter 192 | video_d = luma_black_level + luma_filtered; 193 | 194 | // now add chroma carrier 195 | if (video_standard == SECAM) video_d = video_d + {secam_chroma[7], secam_chroma}; 196 | else video_d = video_d + {pal_ntsc_chroma[7], pal_ntsc_chroma}; 197 | end 198 | end 199 | end 200 | 201 | // Handle debug bus to allow configuratuion 202 | always_ff @(posedge clk) begin 203 | if (dbus.addr[15:8] == 8'h00 && dbus.write_enable) begin 204 | case (dbus.addr[7:0]) 205 | 0: luma_delay_duration <= dbus.write_data[4:0]; 206 | 6: begin 207 | chroma_lowpass_enable <= dbus.write_data[0]; 208 | chroma_bandpass_enable <= dbus.write_data[1]; 209 | chroma_enable <= dbus.write_data[4]; 210 | end 211 | 7: debug_burst_u <= dbus.write_data[5:0]; 212 | 8: debug_burst_v <= dbus.write_data[5:0]; 213 | 9: luma_black_level <= dbus.write_data; 214 | 12: yuv_u_delay_duration <= dbus.write_data[4:0]; 215 | 13: yuv_v_delay_duration <= dbus.write_data[4:0]; 216 | 14: secam_debug_db_swing <= dbus.write_data[7:0]; 217 | 15: secam_debug_dr_swing <= dbus.write_data[6:0]; 218 | 16: secam_debug_carrier_delay <= dbus.write_data[4:0]; 219 | default: ; 220 | endcase 221 | end 222 | end 223 | 224 | // Higher bit width variant of some filter to compare against 225 | // during verilation 226 | `ifdef VERILATOR 227 | bit [7:0] luma_filtered_check; 228 | // verilator lint_off WIDTHEXPAND 229 | pal_verify_lumafilter lumafilter0_check ( 230 | .clk(clk), 231 | .in (delayed.y), 232 | .out(luma_filtered_check) 233 | ); 234 | // verilator lint_on WIDTHEXPAND 235 | always_ff @(posedge clk) begin 236 | assert (luma_filtered == luma_filtered_check); 237 | end 238 | `endif 239 | 240 | 241 | endmodule 242 | -------------------------------------------------------------------------------- /rtl/configuration.svh: -------------------------------------------------------------------------------- 1 | 2 | `define CONFIG_SECAM_ENABLED 3 | `define CONFIG_PAL_NTSC_ENABLED 4 | // `define CONFIG_PAL_NTSC_CHROMA_LOWPASS 5 | 6 | // Optimized for YCbCr 7 | `define CONFIG_PAL_Y_SCALER 125 8 | `define CONFIG_PAL_U_SCALER 41 9 | `define CONFIG_PAL_V_SCALER 58 10 | 11 | `define CONFIG_NTSC_Y_SCALER 125 12 | `define CONFIG_NTSC_U_SCALER 41 13 | `define CONFIG_NTSC_V_SCALER 58 14 | 15 | `define CONFIG_SECAM_Y_SCALER 125 16 | `define CONFIG_SECAM_U_SCALER 36 17 | `define CONFIG_SECAM_V_SCALER 48 18 | 19 | `define CONFIG_SECAM_DB_SWING 33 20 | `define CONFIG_SECAM_DR_SWING 20 21 | -------------------------------------------------------------------------------- /rtl/debug_busmaster.sv: -------------------------------------------------------------------------------- 1 | /* 2 | * Data stream controlled debug bus master 3 | * Can be remote controlled using a byte stream 4 | * which could come from a UART or other sources. 5 | * Reads from and writes to an address 6 | */ 7 | module debug_busmaster ( 8 | input clk, 9 | input [7:0] i_com_data, 10 | input i_com_strobe, 11 | output bit [7:0] o_com_data, 12 | output bit o_com_strobe, 13 | 14 | debug_bus_if.master dbus 15 | ); 16 | 17 | typedef enum bit [3:0] { 18 | IDLE, // Waiting for input 19 | READ_ADDR0, // Expects high byte of address to read from 20 | READ_ADDR1, // Expects low byte of address to read from 21 | READ_DATA, // Reading from provided address 22 | WRITE_ADDR0, // Expects high byte of address to write to 23 | WRITE_ADDR1, // Expects low byte of address to write to 24 | WRITE_DATA, // Expects byte to write to address 25 | WRITE_DATA_PERFORM, // Doing the confgiured write operation 26 | BLOCK_COUNT // Next expected byte is the number of bytes to receive 27 | } states_e; 28 | 29 | states_e state_q = IDLE; 30 | states_e state_d; 31 | 32 | bit [7:0] remaining_bytes = 0; 33 | bit decrement_remaining_bytes; 34 | bit set_remaining_bytes; 35 | bit collect_adr_high; 36 | bit collect_adr_low; 37 | 38 | always_comb begin 39 | o_com_data = dbus.read_data; 40 | dbus.write_enable = 0; 41 | o_com_strobe = 0; 42 | dbus.read_enable = 0; 43 | state_d = state_q; 44 | decrement_remaining_bytes = 0; 45 | set_remaining_bytes = 0; 46 | 47 | collect_adr_high = 0; 48 | collect_adr_low = 0; 49 | 50 | case (state_q) 51 | IDLE: begin 52 | if (i_com_strobe) begin 53 | if (i_com_data == "R") begin 54 | state_d = READ_ADDR0; 55 | end else if (i_com_data == "W") begin 56 | state_d = WRITE_ADDR0; 57 | end else if (i_com_data == "B") begin 58 | state_d = BLOCK_COUNT; 59 | end 60 | end 61 | end 62 | 63 | BLOCK_COUNT: 64 | if (i_com_strobe) begin 65 | state_d = WRITE_ADDR0; 66 | set_remaining_bytes = 1; 67 | end 68 | 69 | WRITE_ADDR0: 70 | if (i_com_strobe) begin 71 | state_d = WRITE_ADDR1; 72 | collect_adr_high = 1; 73 | end 74 | WRITE_ADDR1: 75 | if (i_com_strobe) begin 76 | state_d = WRITE_DATA; 77 | collect_adr_low = 1; 78 | end 79 | WRITE_DATA: if (i_com_strobe) state_d = WRITE_DATA_PERFORM; 80 | WRITE_DATA_PERFORM: begin 81 | dbus.write_enable = 1; 82 | if (dbus.ready) begin 83 | 84 | if (remaining_bytes == 0) begin 85 | state_d = IDLE; 86 | o_com_data = "K"; 87 | o_com_strobe = 1; 88 | end else begin 89 | state_d = WRITE_DATA; 90 | decrement_remaining_bytes = 1; 91 | end 92 | 93 | end 94 | end 95 | 96 | READ_ADDR0: 97 | if (i_com_strobe) begin 98 | state_d = READ_ADDR1; 99 | collect_adr_high = 1; 100 | end 101 | READ_ADDR1: 102 | if (i_com_strobe) begin 103 | state_d = READ_DATA; 104 | collect_adr_low = 1; 105 | end 106 | READ_DATA: begin 107 | dbus.read_enable = 1; 108 | 109 | if (dbus.read_data_valid) begin 110 | state_d = IDLE; 111 | o_com_strobe = 1; 112 | end 113 | end 114 | 115 | default: begin 116 | end 117 | endcase 118 | end 119 | 120 | always_ff @(posedge clk) begin 121 | 122 | if (set_remaining_bytes) remaining_bytes <= i_com_data; 123 | else if (decrement_remaining_bytes) remaining_bytes <= remaining_bytes - 1; 124 | 125 | if (collect_adr_high) dbus.addr[15:8] <= i_com_data; 126 | if (collect_adr_low) dbus.addr[7:0] <= i_com_data; 127 | if (i_com_strobe && state_q == WRITE_DATA) dbus.write_data <= i_com_data; 128 | 129 | state_q <= state_d; 130 | end 131 | 132 | 133 | endmodule 134 | -------------------------------------------------------------------------------- /rtl/delayfifo.sv: -------------------------------------------------------------------------------- 1 | /* 2 | * Implements a delay line with variable latency. 3 | * The maximum possible latency can be defined using parameters. 4 | * With the current implementation, changing the latency on the fly might cause a single maximum latency run 5 | * through the memory. 6 | * The total latency is equal to the provided input +1 as the output is buffered once to reduce propagation delays. 7 | */ 8 | 9 | module delayfifo #( 10 | parameter int BIT_WIDTH = 8, 11 | parameter int SIZE = 5 12 | ) ( 13 | input clk, 14 | input [SIZE-1:0] latency, 15 | input [BIT_WIDTH-1:0] in, 16 | output bit [BIT_WIDTH-1:0] out 17 | ); 18 | 19 | // Storage memory 20 | bit [BIT_WIDTH-1:0] delay_mem[1<>> shift; 35 | end 36 | endfunction 37 | 38 | bit signed [14:0] v; 39 | bit signed [14:0] v_q; 40 | bit signed [10:0] y; 41 | 42 | bit signed [10:0] v0_mul_b0_d; 43 | bit signed [10:0] v0_mul_b0_q; 44 | 45 | always_comb begin 46 | v = rz0_q + 14'(x); 47 | rz0_d = 15'(reduce((A1 * v), Aprecision)) + rz1_q; 48 | rz1_d = 15'(reduce((A2 * v), Aprecision)); 49 | 50 | v0_mul_b0_d = 11'(reduce(B0 * v_q, Bprecision)); 51 | 52 | y = v0_mul_b0_q + lz0_q2; 53 | lz0_d = 11'(reduce((B1 * v_q), Bprecision)) + lz1_q; 54 | lz1_d = 11'(reduce((B2 * v_q), Bprecision)); 55 | end 56 | 57 | always_ff @(posedge clk) begin 58 | x <= 10'(in) <<< 3; // add 1 tick delay but keeps pathes short 59 | 60 | out <= 6'((y + 11'd3) >>> 3); // add 1 tick delay but keeps pathes short 61 | 62 | rz0_q <= rz0_d; 63 | rz1_q <= rz1_d; 64 | lz0_q <= lz0_d; 65 | lz1_q <= lz1_d; 66 | 67 | lz0_q2 <= lz0_q; 68 | v_q <= v; 69 | 70 | v0_mul_b0_q <= v0_mul_b0_d; 71 | end 72 | 73 | endmodule 74 | -------------------------------------------------------------------------------- /rtl/filter/filter_pal_luma.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | 4 | module filter_pal_luma ( 5 | input clk, 6 | input [7:0] in, 7 | output bit [7:0] out 8 | ); 9 | 10 | localparam int B0 = `PAL_LUMA_LOWPASS_B0; 11 | localparam int B1 = `PAL_LUMA_LOWPASS_B1; 12 | localparam int B2 = `PAL_LUMA_LOWPASS_B2; 13 | 14 | localparam int A1 = -(`PAL_LUMA_LOWPASS_A1); 15 | localparam int A2 = -(`PAL_LUMA_LOWPASS_A2); 16 | 17 | localparam int APrecision = `PAL_LUMA_LOWPASS_A_AFTER_DOT; 18 | localparam int BPrecision = `PAL_LUMA_LOWPASS_B_AFTER_DOT; 19 | 20 | bit signed [14:0] rz0_d; 21 | bit signed [14:0] rz1_d; 22 | bit signed [10:0] lz0_d; 23 | bit signed [10:0] lz1_d; 24 | 25 | bit signed [14:0] rz0_q = 0; 26 | bit signed [14:0] rz1_q = 0; 27 | bit signed [10:0] lz0_q = 0; 28 | bit signed [10:0] lz0_q2 = 0; 29 | bit signed [10:0] lz1_q = 0; 30 | 31 | bit [9:0] x; 32 | 33 | function automatic int reduce(input int value, input int shift); 34 | begin 35 | reduce = value >>> shift; 36 | end 37 | endfunction 38 | 39 | bit signed [14:0] v; 40 | bit signed [14:0] v_q; 41 | bit signed [10:0] y; 42 | 43 | bit signed [10:0] v0_mul_b0_d; 44 | bit signed [10:0] v0_mul_b0_q; 45 | 46 | always_comb begin 47 | v = rz0_q + 14'(x); 48 | rz0_d = 15'(reduce((A1 * v), APrecision)) + rz1_q; 49 | rz1_d = 15'(reduce((A2 * v), APrecision)); 50 | 51 | v0_mul_b0_d = 11'(reduce(B0 * v_q, BPrecision)); 52 | 53 | y = v0_mul_b0_q + lz0_q2; 54 | lz0_d = 11'(reduce((B1 * v_q), BPrecision)) + lz1_q; 55 | lz1_d = 11'(reduce((B2 * v_q), BPrecision)); 56 | end 57 | 58 | always_ff @(posedge clk) begin 59 | x <= 10'(in) <<< 2; // add 1 tick delay but keeps pathes short 60 | 61 | // just to be sure that the output is always positive 62 | if (y < 0) out <= 0; 63 | else out <= 8'((y + 11'd2) >>> 2); // add 1 tick delay but keeps pathes short 64 | 65 | rz0_q <= rz0_d; 66 | rz1_q <= rz1_d; 67 | lz0_q <= lz0_d; 68 | lz1_q <= lz1_d; 69 | 70 | lz0_q2 <= lz0_q; 71 | v_q <= v; 72 | 73 | v0_mul_b0_q <= v0_mul_b0_d; 74 | end 75 | 76 | endmodule 77 | -------------------------------------------------------------------------------- /rtl/filter/filter_pal_ntsc_carrier.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | 4 | module filter_pal_ntsc_carrier ( 5 | input clk, 6 | input signed [7:0] in, 7 | input pal_mode, 8 | output bit signed [7:0] out 9 | ); 10 | 11 | /* TODO for GOWIN support 12 | * Something here is wrong. Coefficients A are applied inverted 13 | * compared to the B coefficients. 14 | * Previously it was like this 15 | * rz0_d = 11'(reduce((-a1 * v), a_precision)) + rz1_q; 16 | * but now it is like this 17 | * rz0_d = 11'(reduce((a1 * v), a_precision)) + rz1_q; 18 | * and the coefficient is instead inverted here at the top. 19 | */ 20 | localparam int PalB0 = `PAL_CHROMA_B0; 21 | localparam int PalB1 = `PAL_CHROMA_B1; 22 | localparam int PalB2 = `PAL_CHROMA_B2; 23 | localparam int PalA1 = -(`PAL_CHROMA_A1); 24 | localparam int PalA2 = -(`PAL_CHROMA_A2); 25 | 26 | localparam int NtscB0 = `NTSC_CHROMA_B0; 27 | localparam int NtscB1 = `NTSC_CHROMA_B1; 28 | localparam int NtscB2 = `NTSC_CHROMA_B2; 29 | localparam int NtscA1 = -(`NTSC_CHROMA_A1); 30 | localparam int NtscA2 = -(`NTSC_CHROMA_A2); 31 | 32 | int b0; 33 | int b1; 34 | int b2; 35 | int a1; 36 | int a2; 37 | 38 | always_comb begin 39 | if (pal_mode) begin 40 | b0 = PalB0; 41 | b1 = PalB1; 42 | b2 = PalB2; 43 | a1 = PalA1; 44 | a2 = PalA2; 45 | end else begin 46 | b0 = NtscB0; 47 | b1 = NtscB1; 48 | b2 = NtscB2; 49 | a1 = NtscA1; 50 | a2 = NtscA2; 51 | end 52 | 53 | end 54 | 55 | localparam int APrecision = `PAL_CHROMA_A_AFTER_DOT; 56 | localparam int BPrecision = `PAL_CHROMA_B_AFTER_DOT; 57 | 58 | bit signed [10:0] rz0_d; 59 | bit signed [10:0] rz1_d; 60 | bit signed [ 7:0] lz0_d; 61 | bit signed [ 7:0] lz1_d; 62 | 63 | bit signed [10:0] rz0_q = 0; 64 | bit signed [10:0] rz1_q = 0; 65 | bit signed [ 7:0] lz0_q = 0; 66 | bit signed [ 7:0] lz0_q2 = 0; 67 | bit signed [ 7:0] lz1_q = 0; 68 | 69 | bit signed [ 7:0] x; 70 | 71 | function automatic int reduce(input int value, input int shift); 72 | begin 73 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 74 | end 75 | endfunction 76 | 77 | bit signed [10:0] v; 78 | bit signed [ 8:0] y; 79 | 80 | bit signed [ 8:0] v0_mul_b0_d; 81 | bit signed [ 8:0] v0_mul_b0_q; 82 | 83 | always_comb begin 84 | v = rz0_q + 11'(x); 85 | rz0_d = 11'(reduce((a1 * v), APrecision)) + rz1_q; 86 | rz1_d = 11'(reduce((a2 * v), APrecision)); 87 | 88 | v0_mul_b0_d = 9'(reduce(b0 * v, BPrecision)); 89 | 90 | y = v0_mul_b0_q + lz0_q2; 91 | lz0_d = 8'(reduce((b1 * v), BPrecision)) + lz1_q; 92 | lz1_d = 8'(reduce((b2 * v), BPrecision)); 93 | end 94 | 95 | always_ff @(posedge clk) begin 96 | x <= in; // add 1 tick delay but keeps pathes short 97 | out <= 8'(y); // add 1 tick delay but keeps pathes short 98 | 99 | rz0_q <= rz0_d; 100 | rz1_q <= rz1_d; 101 | lz0_q <= lz0_d; 102 | lz1_q <= lz1_d; 103 | 104 | lz0_q2 <= lz0_q; 105 | 106 | v0_mul_b0_q <= v0_mul_b0_d; 107 | 108 | end 109 | 110 | endmodule 111 | -------------------------------------------------------------------------------- /rtl/filter/filter_secam_amplitude_lowpass.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module filter_secam_amplitude_lowpass ( 4 | input clk, 5 | input [5:0] in, 6 | output bit [5:0] out 7 | ); 8 | /* TODO for GOWIN support 9 | * Something here is wrong. Coefficients A are applied inverted 10 | * compared to the B coefficients. 11 | * Previously it was like this 12 | * rz0_d = 11'(reduce((-a1 * v), a_precision)) + rz1_q; 13 | * but now it is like this 14 | * rz0_d = 11'(reduce((a1 * v), a_precision)) + rz1_q; 15 | * and the coefficient is instead inverted here at the top. 16 | */ 17 | localparam int B0 = `SECAM_AMPLITUDE_LOWPASS_B0; 18 | localparam int B1 = `SECAM_AMPLITUDE_LOWPASS_B1; 19 | localparam int A1 = -(`SECAM_AMPLITUDE_LOWPASS_A1); 20 | 21 | localparam int APrecision = `SECAM_AMPLITUDE_LOWPASS_A_AFTER_DOT; 22 | localparam int BPrecision = `SECAM_AMPLITUDE_LOWPASS_B_AFTER_DOT; 23 | 24 | bit signed [9:0] rz0_d; 25 | bit signed [7:0] lz0_d; 26 | 27 | bit signed [9:0] rz0_q = 0; 28 | bit signed [7:0] lz0_q = 0; 29 | bit signed [7:0] lz0_q2 = 0; 30 | 31 | bit [5:0] x; 32 | 33 | function automatic int reduce(input int value, input int shift); 34 | begin 35 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 36 | end 37 | endfunction 38 | 39 | bit signed [9:0] v; 40 | bit signed [7:0] y; 41 | 42 | bit signed [7:0] v0_mul_b0_d; 43 | bit signed [7:0] v0_mul_b0_q; 44 | 45 | always_comb begin 46 | v = rz0_q + 10'(x); 47 | rz0_d = 10'(reduce((A1 * v), APrecision)); 48 | 49 | v0_mul_b0_d = 8'(reduce(B0 * v, BPrecision)); 50 | 51 | y = v0_mul_b0_q + lz0_q2; 52 | lz0_d = 8'(reduce((B1 * v), BPrecision)); 53 | end 54 | 55 | always_ff @(posedge clk) begin 56 | x <= in; // add 1 tick delay but keeps pathes short 57 | 58 | if (y < 0) out <= 0; 59 | else out <= 6'(y); // add 1 tick delay but keeps pathes short 60 | 61 | rz0_q <= rz0_d; 62 | lz0_q <= lz0_d; 63 | 64 | lz0_q2 <= lz0_q; 65 | v0_mul_b0_q <= v0_mul_b0_d; 66 | end 67 | 68 | endmodule 69 | -------------------------------------------------------------------------------- /rtl/filter/filter_secam_chroma_lowpass.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module filter_secam_chroma_lowpass ( 4 | input clk, 5 | input signed [8:0] in, 6 | output bit signed [8:0] out 7 | ); 8 | /* TODO for GOWIN support 9 | * Something here is wrong. Coefficients A are applied inverted 10 | * compared to the B coefficients. 11 | * Previously it was like this 12 | * rz0_d = 11'(reduce((-a1 * v), a_precision)) + rz1_q; 13 | * but now it is like this 14 | * rz0_d = 11'(reduce((a1 * v), a_precision)) + rz1_q; 15 | * and the coefficient is instead inverted here at the top. 16 | */ 17 | localparam int B0 = `SECAM_CHROMA_LOWPASS_B0; 18 | localparam int B1 = `SECAM_CHROMA_LOWPASS_B1; 19 | localparam int A1 = -(`SECAM_CHROMA_LOWPASS_A1); 20 | 21 | localparam int APrecision = `SECAM_CHROMA_LOWPASS_A_AFTER_DOT; 22 | localparam int BPrecision = `SECAM_CHROMA_LOWPASS_B_AFTER_DOT; 23 | 24 | bit signed [9:0] rz0_d; 25 | bit signed [9:0] lz0_d; 26 | 27 | bit signed [9:0] rz0_q = 0; 28 | bit signed [9:0] lz0_q = 0; 29 | bit signed [9:0] lz0_q2 = 0; 30 | 31 | bit signed [9:0] x; 32 | 33 | function automatic int reduce(input int value, input int shift); 34 | begin 35 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 36 | end 37 | endfunction 38 | 39 | bit signed [9:0] v; 40 | bit signed [9:0] v_q; 41 | bit signed [9:0] y; 42 | 43 | bit signed [9:0] v0_mul_b0_d; 44 | bit signed [9:0] v0_mul_b0_q; 45 | 46 | always_comb begin 47 | v = rz0_q + x; 48 | rz0_d = 10'(reduce((A1 * v), APrecision)); 49 | 50 | v0_mul_b0_d = 10'(reduce(B0 * v_q, BPrecision)); 51 | 52 | y = v0_mul_b0_q + lz0_q2; 53 | lz0_d = 10'(reduce((B1 * v_q), BPrecision)); 54 | end 55 | 56 | always_ff @(posedge clk) begin 57 | x <= 10'(in) <<< 1; // add 1 tick delay but keeps pathes short 58 | out <= 9'(y >>> 1); // add 1 tick delay but keeps pathes short 59 | 60 | rz0_q <= rz0_d; 61 | lz0_q <= lz0_d; 62 | 63 | lz0_q2 <= lz0_q; 64 | v_q <= v; 65 | 66 | v0_mul_b0_q <= v0_mul_b0_d; 67 | end 68 | 69 | endmodule 70 | -------------------------------------------------------------------------------- /rtl/filter/filter_secam_deemphasis.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module filter_secam_deemphasis ( 4 | input clk, 5 | input signed [8:0] in, 6 | output bit signed [8:0] out 7 | ); 8 | /* TODO for GOWIN support 9 | * Something here is wrong. Coefficients A are applied inverted 10 | * compared to the B coefficients. 11 | * Previously it was like this 12 | * rz0_d = 11'(reduce((-a1 * v), a_precision)) + rz1_q; 13 | * but now it is like this 14 | * rz0_d = 11'(reduce((a1 * v), a_precision)) + rz1_q; 15 | * and the coefficient is instead inverted here at the top. 16 | */ 17 | localparam int B0 = `SECAM_PREEMPHASIS_B0; 18 | localparam int B1 = `SECAM_PREEMPHASIS_B1; 19 | localparam int A1 = -(`SECAM_PREEMPHASIS_A1); 20 | 21 | localparam int APrecision = `SECAM_PREEMPHASIS_A_AFTER_DOT; 22 | localparam int BPrecision = `SECAM_PREEMPHASIS_B_AFTER_DOT; 23 | 24 | bit signed [13:0] rz0_d; 25 | bit signed [ 9:0] lz0_d; 26 | 27 | bit signed [13:0] rz0_q = 0; 28 | bit signed [ 9:0] lz0_q = 0; 29 | bit signed [ 9:0] lz0_q2 = 0; 30 | 31 | bit signed [ 9:0] x; 32 | 33 | function automatic int reduce(input int value, input int shift); 34 | begin 35 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 36 | end 37 | endfunction 38 | 39 | function automatic int reduce2(input int value, input int shift); 40 | begin 41 | reduce2 = value >>> shift; 42 | end 43 | endfunction 44 | 45 | bit signed [13:0] v; 46 | bit signed [13:0] v_q; 47 | bit signed [ 9:0] y; 48 | 49 | bit signed [ 9:0] v0_mul_b0_d; 50 | bit signed [ 9:0] v0_mul_b0_q; 51 | 52 | always_comb begin 53 | v = rz0_q + 13'(x); 54 | rz0_d = 14'(reduce2((A1 * v), APrecision)); 55 | 56 | v0_mul_b0_d = 10'(reduce(B0 * v_q, BPrecision)); 57 | 58 | y = v0_mul_b0_q + lz0_q2; 59 | lz0_d = 10'(reduce((B1 * v_q), BPrecision)); 60 | end 61 | 62 | always_ff @(posedge clk) begin 63 | x <= 10'(in) <<< 1; // add 1 tick delay but keeps pathes short 64 | out <= 9'(y >>> 1); // add 1 tick delay but keeps pathes short 65 | 66 | rz0_q <= rz0_d; 67 | lz0_q <= lz0_d; 68 | 69 | lz0_q2 <= lz0_q; 70 | v_q <= v; 71 | 72 | v0_mul_b0_q <= v0_mul_b0_d; 73 | end 74 | 75 | endmodule 76 | -------------------------------------------------------------------------------- /rtl/logic_analyzer.sv: -------------------------------------------------------------------------------- 1 | module logic_analyzer ( 2 | debug_bus_if.slave dbus, 3 | input trigger, 4 | input [31:0] input_data 5 | ); 6 | 7 | bit [31:0] mem[64]; 8 | bit [5:0] write_position = 0; 9 | bit activated = 0; 10 | bit [31:0] read_word; 11 | bit [5:0] remaining = 0; 12 | 13 | always_comb begin 14 | dbus.ready = 1; 15 | 16 | if (dbus.addr[8]) dbus.read_data = {1'b0, write_position, activated}; 17 | else begin 18 | case (dbus.addr[1:0]) 19 | 0: dbus.read_data = read_word[31:24]; 20 | 1: dbus.read_data = read_word[23:16]; 21 | 2: dbus.read_data = read_word[15:8]; 22 | 3: dbus.read_data = read_word[7:0]; 23 | default: ; 24 | endcase 25 | end 26 | end 27 | 28 | always_ff @(posedge dbus.clk) begin 29 | dbus.read_data_valid <= dbus.read_enable; 30 | read_word <= mem[dbus.addr[7:2]]; 31 | 32 | if (remaining != 5) begin 33 | mem[write_position] <= input_data; 34 | write_position <= write_position + 1; 35 | 36 | if (activated) remaining <= remaining + 1; 37 | end 38 | 39 | if (trigger) activated <= 1; 40 | end 41 | 42 | endmodule 43 | -------------------------------------------------------------------------------- /rtl/memory_tester.sv: -------------------------------------------------------------------------------- 1 | 2 | module memory_tester ( 3 | burst_bus_if.master mem, 4 | output bit error 5 | ); 6 | bit [7:0] cnt = 0; 7 | bit [7:0] state_d; 8 | bit [7:0] state_q = 0; 9 | bit [7:0] collected_words = 0; 10 | bit cmd = 1; 11 | bit increment_collected_words; 12 | bit [63:0] data; 13 | bit next_cycle; 14 | 15 | bit [63:0] memory_addr = 0; 16 | 17 | initial begin 18 | error = 0; 19 | cnt = 0; 20 | end 21 | 22 | always_comb begin 23 | state_d = state_q; 24 | mem.cmd_en = 0; 25 | increment_collected_words = 0; 26 | next_cycle = 0; 27 | mem.addr = memory_addr[20:0]; 28 | 29 | case (state_q) 30 | 0: begin 31 | if (!error) begin 32 | mem.cmd_en = 1; 33 | 34 | if (mem.ready) begin 35 | state_d = 1; 36 | 37 | if (cmd) increment_collected_words = 1; 38 | end 39 | end 40 | end 41 | 25: begin 42 | state_d = 0; 43 | next_cycle = 1; 44 | end 45 | default: begin 46 | state_d = state_q + 1; 47 | if (cmd) increment_collected_words = 1; 48 | end 49 | endcase 50 | 51 | if (!cmd && mem.rd_data_valid) increment_collected_words = 1; 52 | 53 | data[63:56] = cnt + 0 + collected_words * 8; 54 | data[55:48] = cnt + 1 + collected_words * 8; 55 | data[47:40] = cnt + 2 + collected_words * 8; 56 | data[39:32] = cnt + 3 + collected_words * 8; 57 | data[31:24] = cnt + 4 + collected_words * 8; 58 | data[23:16] = cnt + 5 + collected_words * 8; 59 | data[15:8] = cnt + 6 + collected_words * 8; 60 | data[7:0] = cnt + 7 + collected_words * 8; 61 | 62 | mem.cmd = cmd; 63 | mem.data_mask = 0; 64 | 65 | mem.wr_data = data; 66 | end 67 | 68 | always_ff @(posedge mem.clk) begin 69 | 70 | if (increment_collected_words && cmd && state_q < 4) 71 | $display("Writing %0h %d %d ", data, state_d, cmd); 72 | 73 | state_q <= state_d; 74 | 75 | if (next_cycle) begin 76 | cmd <= !cmd; 77 | collected_words <= 0; 78 | 79 | if (!cmd) begin 80 | cnt <= cnt + 1; 81 | memory_addr <= memory_addr + 1; 82 | 83 | end 84 | end else if (mem.rd_data_valid) begin 85 | if (mem.rd_data != mem.wr_data) error <= 1; 86 | 87 | $display("Compare %d %0h %0h ", state_d, mem.rd_data, mem.wr_data); 88 | end 89 | 90 | 91 | if (increment_collected_words) collected_words <= collected_words + 1; 92 | 93 | end 94 | 95 | endmodule 96 | -------------------------------------------------------------------------------- /rtl/pal_ntsc_encoder.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | /* 4 | * PAL NTSC color carrier generator 5 | */ 6 | module pal_ntsc_encoder ( 7 | input clk, 8 | input newframe, 9 | input newline, 10 | input even_line, 11 | input pal_mode, // 1 == PAL, 0 == NTSC 12 | input chroma_lowpass_enable, 13 | input chroma_bandpass_enable, 14 | input signed [7:0] yuv_u, 15 | input signed [7:0] yuv_v, 16 | input startburst, // Flag to start the color burst 17 | output bit signed [7:0] chroma, 18 | input signed [5:0] debug_burst_u, 19 | input signed [5:0] debug_burst_v 20 | ); 21 | 22 | `ifdef CONFIG_PAL_NTSC_CHROMA_LOWPASS 23 | // Perform low pass filtering for testing 24 | bit signed [5:0] yuv_u_filtered; 25 | bit signed [5:0] yuv_v_filtered; 26 | filter_pal_chroma_lowpass clow0 ( 27 | .clk(clk), 28 | .in (yuv_u[5:0]), 29 | .out(yuv_u_filtered) 30 | ); 31 | 32 | filter_pal_chroma_lowpass clow1 ( 33 | .clk(clk), 34 | .in (yuv_v[5:0]), 35 | .out(yuv_v_filtered) 36 | ); 37 | `else 38 | wire signed [5:0] yuv_u_filtered = yuv_u[5:0]; 39 | wire signed [5:0] yuv_v_filtered = yuv_v[5:0]; 40 | `endif 41 | 42 | // We start by performing quadrature amplitude modulation 43 | // The result will have frequencies above and below the carrier 44 | // and thus will be defined as unfiltered here 45 | bit signed [7:0] chroma_unfiltered /*verilator public_flat_rd*/; 46 | qam qam0 ( 47 | .clk(clk), 48 | .newframe(newframe), 49 | .newline(newline), 50 | .startburst(startburst), 51 | .pal_mode, 52 | .in_u(chroma_lowpass_enable ? yuv_u_filtered : yuv_u[5:0]), 53 | .in_v(chroma_lowpass_enable ? yuv_v_filtered : yuv_v[5:0]), 54 | .even_line(even_line), 55 | .chroma(chroma_unfiltered), 56 | .debug_burst_u, 57 | .debug_burst_v 58 | ); 59 | 60 | // The chroma signal must then be filtered using a band pass to remove 61 | // higher and lower frequencies to avoid bleeding into the luma signal 62 | // manifesting as dot crawl 63 | bit signed [7:0] chroma_filtered /*verilator public_flat_rd*/; 64 | filter_pal_ntsc_carrier chromafilter0 ( 65 | .clk(clk), 66 | .pal_mode, 67 | .in (chroma_unfiltered), 68 | .out(chroma_filtered) 69 | ); 70 | 71 | assign chroma = chroma_bandpass_enable ? chroma_filtered : chroma_unfiltered; 72 | 73 | // To check if the bit width of the filters is ok, we instantiate them again 74 | // with a higher bit width and let them run in lock step. The results must match 75 | `ifdef VERILATOR 76 | bit signed [7:0] chroma_filtered_check; 77 | bit signed [7:0] chroma_filtered_check_q2; 78 | 79 | // verilator lint_off WIDTHEXPAND 80 | localparam int ChromaFiltWidth = 23; 81 | 82 | bit signed [ChromaFiltWidth:0] chroma_filter_b0; 83 | bit signed [ChromaFiltWidth:0] chroma_filter_b1; 84 | bit signed [ChromaFiltWidth:0] chroma_filter_b2; 85 | bit signed [ChromaFiltWidth:0] chroma_filter_b3; 86 | bit signed [ChromaFiltWidth:0] chroma_filter_b4; 87 | 88 | bit signed [ChromaFiltWidth:0] chroma_filter_a1; 89 | bit signed [ChromaFiltWidth:0] chroma_filter_a2; 90 | bit signed [ChromaFiltWidth:0] chroma_filter_a3; 91 | bit signed [ChromaFiltWidth:0] chroma_filter_a4; 92 | 93 | always_comb begin 94 | if (pal_mode) begin 95 | chroma_filter_b0 = `PAL_CHROMA_B0; 96 | chroma_filter_b1 = `PAL_CHROMA_B1; 97 | chroma_filter_b2 = `PAL_CHROMA_B2; 98 | chroma_filter_b3 = 0; 99 | chroma_filter_b4 = 0; 100 | chroma_filter_a1 = `PAL_CHROMA_A1; 101 | chroma_filter_a2 = `PAL_CHROMA_A2; 102 | chroma_filter_a3 = 0; 103 | chroma_filter_a4 = 0; 104 | end else begin 105 | chroma_filter_b0 = `NTSC_CHROMA_B0; 106 | chroma_filter_b1 = `NTSC_CHROMA_B1; 107 | chroma_filter_b2 = `NTSC_CHROMA_B2; 108 | chroma_filter_b3 = 0; 109 | chroma_filter_b4 = 0; 110 | chroma_filter_a1 = `NTSC_CHROMA_A1; 111 | chroma_filter_a2 = `NTSC_CHROMA_A2; 112 | chroma_filter_a3 = 0; 113 | chroma_filter_a4 = 0; 114 | end 115 | end 116 | 117 | filter_int_5tap chromafilter_check ( 118 | .clk(clk), 119 | .in(chroma_unfiltered), 120 | .out(chroma_filtered_check), 121 | .b0(chroma_filter_b0), 122 | .b1(chroma_filter_b1), 123 | .b2(chroma_filter_b2), 124 | .b3(chroma_filter_b3), 125 | .b4(chroma_filter_b4), 126 | .a1(chroma_filter_a1), 127 | .a2(chroma_filter_a2), 128 | .a3(chroma_filter_a3), 129 | .a4(chroma_filter_a4), 130 | .a_precision(`PAL_CHROMA_A_AFTER_DOT), 131 | .b_precision(`PAL_CHROMA_B_AFTER_DOT) 132 | ); 133 | 134 | // verilator lint_on WIDTHEXPAND 135 | 136 | always_ff @(posedge clk) begin 137 | chroma_filtered_check_q2 <= chroma_filtered_check; 138 | assert (chroma_filtered == chroma_filtered_check_q2); 139 | end 140 | `endif 141 | 142 | 143 | endmodule 144 | 145 | -------------------------------------------------------------------------------- /rtl/pixel_convolver.sv: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Extracts 24 bit words from a data stream consisting of 32 bit words. 4 | * The common factor is 24*4 = 32*3 = 96 5 | * For flow control, this module works pull and not push based. 6 | * 7 | * basic principle: 8 | * Word 0 -> 0120 the last 8 bit have to be stored temporary 9 | * Word 1 -> 1201 the last 16 bit have to be stored temporary 10 | * Word 2 -> 2012 the last 24 bit have to be stored temporary 11 | */ 12 | module pixel_convolver ( 13 | input clk, 14 | input reset, 15 | input mode32, 16 | input [31:0] in32, // 32 bit input data 17 | output bit strobe_input, // flag to present that in32 can be changed 18 | input input_valid, // indicates that in32 is allowed to be used 19 | 20 | output bit [23:0] out24, // 24 bit output data 21 | output bit out24_ready, // out24 is valid and strobe_out24 may be used 22 | input strobe_out24 // flag to indicate that the current out24 was used 23 | ); 24 | bit [23:0] temp_mem = 0; 25 | bit [1:0] state = 0; 26 | bit update_output; 27 | 28 | always_comb begin 29 | 30 | // If the state is <= 2 we need data from outside to continue. 31 | // Fetch data in case we don't have an output yet or if for output is asked. 32 | // For state == 3 there is no data fetching from outside. 33 | if (state <= 2) update_output = ((!out24_ready || strobe_out24) && input_valid); 34 | else update_output = strobe_out24; 35 | 36 | strobe_input = update_output && state <= 2; 37 | end 38 | 39 | always_ff @(posedge clk) begin 40 | 41 | if (reset) begin 42 | out24 <= 0; 43 | state <= 0; 44 | temp_mem <= 0; 45 | out24_ready <= 0; 46 | end else begin 47 | if (strobe_out24) out24_ready <= 0; 48 | 49 | if (update_output) begin 50 | out24_ready <= 1; 51 | 52 | if (mode32) begin 53 | // just pass through 54 | out24 <= in32[23:0]; 55 | end else begin 56 | case (state) 57 | 0: begin 58 | // make use of the first 24 bit and store the last 8 bit 59 | out24 <= in32[31:8]; 60 | //$display("state0 out24 %x", in32[31:8]); 61 | temp_mem <= {16'b0, in32[7:0]}; 62 | state <= 1; 63 | end 64 | 1: begin 65 | // make use of the stored 8 bit and the first 16 bit from in32 66 | // store the last 16 bit from in32 67 | out24 <= {temp_mem[7:0], in32[31:16]}; 68 | //$display("state1 out24 %x", {temp_mem[7:0], in32[31:16]}); 69 | temp_mem <= {8'b0, in32[15:0]}; 70 | state <= 2; 71 | end 72 | 2: begin 73 | // make use of the stored 16 bit and the first 8 bit from in 74 | // store the last 24 bit 75 | out24 <= {temp_mem[15:0], in32[31:24]}; 76 | //$display("state2 out24 %x", {temp_mem[15:0], in32[31:24]}); 77 | temp_mem <= in32[23:0]; 78 | state <= 3; 79 | end 80 | 3: begin 81 | // use only the internal memory 82 | out24 <= temp_mem; 83 | //$display("state3 out24 %x", temp_mem); 84 | state <= 0; 85 | end 86 | default: begin 87 | // do nothing 88 | end 89 | endcase 90 | end 91 | end 92 | end 93 | 94 | end 95 | endmodule 96 | -------------------------------------------------------------------------------- /rtl/qam.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | /* 4 | * Quadrature amplitude modulation 5 | * using direct digital synthesis (DDS) to generate two sine waves orthogonal to 6 | * each other. 7 | */ 8 | module qam ( 9 | input clk, 10 | input startburst, // Flag to start the color burst 11 | input pal_mode, // 1 == PAL, 0 == NTSC 12 | input signed [5:0] in_u, 13 | input signed [5:0] in_v, 14 | input newline, 15 | input even_line, 16 | input newframe, 17 | output bit signed [7:0] chroma, 18 | input signed [5:0] debug_burst_u, 19 | input signed [5:0] debug_burst_v 20 | ) /* synthesis syn_dspstyle = "logic" */; 21 | 22 | localparam int ClockDivideLastBit = 50; 23 | 24 | // 2.2 usec for 10 cycles 25 | localparam bit [7:0] BurstLen_2_2us = 8'(integer'(2.2 / `CLK_PERIOD_USEC)); 26 | // 2.9 usec for 10 cycles 27 | localparam bit [7:0] BurstLen_2_9us = 8'(integer'(2.9 / `CLK_PERIOD_USEC)); 28 | 29 | bit [ClockDivideLastBit:0] phase_accumulator = 0; 30 | 31 | // Use the highest 5 bit of the phase accumulator as current index 32 | // for the sine wave look up table 33 | wire [4:0] carrier_phase = phase_accumulator[ClockDivideLastBit:ClockDivideLastBit-4]; 34 | 35 | // U phase is equal to carrier 36 | wire [4:0] phase_u = carrier_phase; 37 | // V phase is 90% rotated to U 38 | wire [4:0] phase_v = carrier_phase + 8; 39 | 40 | // Keep track on the remaining clock cycles, the burst needs to stay 41 | bit [7:0] burst_counter = 0; 42 | wire burst_enabled = (burst_counter != 0); 43 | 44 | bit signed [5:0] u; 45 | bit signed [5:0] v; 46 | 47 | // Handle color burst and V inversion for PAL 48 | always_comb begin 49 | // In no special case, let the input data through 50 | u = in_u; 51 | v = in_v; 52 | 53 | if (pal_mode) begin 54 | if (burst_enabled) begin 55 | // Provide 45 degree 56 | u = `PAL_BURST_U; 57 | v = `PAL_BURST_V; 58 | end 59 | 60 | // Do the PAL V inversion thing every other scanline 61 | if (even_line) v = -v; 62 | end else begin 63 | if (burst_enabled) begin 64 | u = debug_burst_u; 65 | v = debug_burst_v; 66 | end 67 | end 68 | 69 | end 70 | 71 | // Handle burst starting and DDS phase accumulation 72 | always @(posedge clk) begin 73 | if (startburst) burst_counter <= pal_mode ? BurstLen_2_2us : BurstLen_2_9us; 74 | else if (burst_counter != 0) burst_counter <= burst_counter - 1; 75 | 76 | if (pal_mode) phase_accumulator <= phase_accumulator + `PAL_CHROMA_DDS_INCREMENT; 77 | else phase_accumulator <= phase_accumulator + `NTSC_CHROMA_DDS_INCREMENT; 78 | end 79 | 80 | wire signed [7:0] sinus_out_u; 81 | wire signed [7:0] sinus_out_v; 82 | 83 | sinus sine_u ( 84 | .clk(clk), 85 | .phase(phase_u), 86 | .amplitude(u), 87 | .out(sinus_out_u) 88 | ); 89 | 90 | sinus sine_v ( 91 | .clk(clk), 92 | .phase(phase_v), 93 | .amplitude(v), 94 | .out(sinus_out_v) 95 | ); 96 | 97 | always @(posedge clk) begin 98 | chroma <= sinus_out_u + sinus_out_v; 99 | end 100 | 101 | endmodule 102 | -------------------------------------------------------------------------------- /rtl/rgbbars.sv: -------------------------------------------------------------------------------- 1 | `include "common.svh" 2 | import common::*; 3 | /* 4 | * Generates 4 color bars according to the EBU standard. 5 | * It expected 256 pixels per scanline equally clocked using newpixel. 6 | */ 7 | 8 | module rgbbars ( 9 | input clk, 10 | input newline, 11 | input newpixel, 12 | 13 | input [7:0] video_y, 14 | 15 | input bit visible_window, 16 | output ycbcr_t out 17 | ); 18 | 19 | bit [7:0] R_d; 20 | bit [7:0] G_d; 21 | bit [7:0] B_d; 22 | bit [7:0] R_q; 23 | bit [7:0] G_q; 24 | bit [7:0] B_q; 25 | 26 | rgb_t rgb_conv_in; 27 | assign rgb_conv_in.r = R_q; 28 | assign rgb_conv_in.g = G_q; 29 | assign rgb_conv_in.b = B_q; 30 | 31 | 32 | RGB2YCbCr rgb_conv ( 33 | .clk, 34 | .in (rgb_conv_in), 35 | .out(out) 36 | ); 37 | 38 | bit [8:0] pixel_x = 0; 39 | bit [2:0] rgb; 40 | bit [2:0] index; 41 | wire [7:0] strength = video_y[6] ? 255 : 191; // 100% and 75% 42 | 43 | always_ff @(posedge clk) begin 44 | if (newline) pixel_x <= 0; 45 | if (visible_window && newpixel) pixel_x <= pixel_x + 1; 46 | 47 | R_q <= R_d; 48 | G_q <= G_d; 49 | B_q <= B_d; 50 | end 51 | 52 | always_comb begin 53 | rgb = 0; 54 | index = pixel_x[7:7-2]; 55 | 56 | // reverse order 57 | if (video_y[7]) index = 7 - index; 58 | 59 | if (visible_window && !pixel_x[8]) begin 60 | case (index) 61 | 3'd0: rgb = 3'b111; 62 | 3'd1: rgb = 3'b110; 63 | 3'd2: rgb = 3'b011; 64 | 3'd3: rgb = 3'b010; 65 | 3'd4: rgb = 3'b101; 66 | 3'd5: rgb = 3'b100; 67 | 3'd6: rgb = 3'b001; 68 | 3'd7: rgb = 3'b000; 69 | default: ; 70 | endcase 71 | end 72 | 73 | R_d = rgb[2] ? strength : 0; 74 | G_d = rgb[1] ? strength : 0; 75 | B_d = rgb[0] ? strength : 0; 76 | end 77 | endmodule 78 | -------------------------------------------------------------------------------- /rtl/secam_ampl.sv: -------------------------------------------------------------------------------- 1 | 2 | module secam_ampl ( 3 | input clk, 4 | input [50:0] phase_inc, 5 | output bit [5:0] out_ampl 6 | ); 7 | wire [10:0] index = 11'(phase_inc[50:36]) - 11'(2048); 8 | bit [5:0] lut[2048]; 9 | 10 | initial begin 11 | $readmemh("../mem/secam_ampl.txt", lut); 12 | end 13 | 14 | always @(posedge clk) begin 15 | out_ampl <= lut[index]; 16 | end 17 | 18 | endmodule 19 | -------------------------------------------------------------------------------- /rtl/secam_encoder.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module secam_encoder ( 4 | input clk, 5 | input even_line, 6 | input signed [7:0] yuv_u, 7 | input signed [7:0] yuv_v, 8 | input enabled, 9 | input chroma_lowpass_enable, 10 | input [7:0] luma_filtered, 11 | input signed [7:0] debug_db_swing, 12 | input signed [6:0] debug_dr_swing, 13 | input [4:0] carrier_period_delay, 14 | input newframe, 15 | output bit signed [7:0] chroma 16 | 17 | ); 18 | localparam int ClockDivideLastBit = 50; 19 | 20 | bit [ClockDivideLastBit:0] clockdivide_counter = 0; 21 | bit [ClockDivideLastBit:0] phase_increment; 22 | bit [ClockDivideLastBit:0] phase_increment_ampl; 23 | bit [4:0] carrier_phase; 24 | 25 | bit [5:0] carrier_amplitude; 26 | bit [5:0] enabled_amplitude; 27 | secam_ampl ampl ( 28 | .clk, 29 | .phase_inc(phase_increment_ampl), 30 | .out_ampl (enabled_amplitude) 31 | ); 32 | bit [5:0] enabled_amplitude_filtered; 33 | 34 | filter_secam_amplitude_lowpass amplow ( 35 | .clk, 36 | .in (enabled_amplitude), 37 | .out(enabled_amplitude_filtered) 38 | ); 39 | 40 | bit signed [8:0] carrier_period_modulate; 41 | 42 | always_comb begin 43 | carrier_amplitude = 0; 44 | carrier_period_modulate = 0; 45 | 46 | carrier_phase = clockdivide_counter[ClockDivideLastBit:ClockDivideLastBit-4]; 47 | 48 | if (enabled) begin 49 | carrier_amplitude = enabled_amplitude_filtered; 50 | 51 | // perform the mux as Db and Dr are transmitted line alternating 52 | if (even_line) begin 53 | carrier_period_modulate = 9'(yuv_u); // Db or U 54 | end else begin 55 | carrier_period_modulate = 9'(yuv_v); // Dr or V 56 | end 57 | 58 | end 59 | end 60 | 61 | // optional low pass filter to remove high frequencies from the color difference signals. 62 | // luckily for SECAM we need only one as only one component is transmitted per line 63 | bit signed [8:0] carrier_period_filtered /*verilator public_flat_rd*/; 64 | bit signed [8:0] carrier_period_deemphasis /*verilator public_flat_rd*/; 65 | filter_secam_chroma_lowpass carrier_lowpass ( 66 | .clk(clk), 67 | .in (carrier_period_modulate), 68 | .out(carrier_period_filtered) 69 | ); 70 | // muxing, making the chroma lowpass optional 71 | bit signed [8:0] carrier_period_maybe_filtered = 0; 72 | 73 | always_ff @(posedge clk) begin 74 | carrier_period_maybe_filtered <= chroma_lowpass_enable ? carrier_period_filtered : carrier_period_modulate; 75 | end 76 | 77 | // perform internal deemphasis in a closed feedback loop 78 | bit signed [12:0] carrier_period_emphasis; 79 | filter_secam_deemphasis deemphasis ( 80 | .clk(clk), 81 | .in (carrier_period_emphasis[12:4]), 82 | .out(carrier_period_deemphasis) 83 | ); 84 | 85 | bit signed [12:0] carrier_period_emphasis_delayed; 86 | delayfifo #(13) df ( 87 | .clk, 88 | .in(carrier_period_emphasis), 89 | .latency(carrier_period_delay), 90 | .out(carrier_period_emphasis_delayed) 91 | ); 92 | 93 | wire signed [12:0] err = 2 * (13'(carrier_period_maybe_filtered) - 13'(carrier_period_deemphasis)); 94 | 95 | // calculate emphasis swing using deemphasis result 96 | always_ff @(posedge clk) begin 97 | if (even_line) begin // Db or U 98 | carrier_period_emphasis <= (debug_db_swing * err) + (13'(carrier_period_deemphasis) <<< 4); 99 | end else begin // Dr or V 100 | carrier_period_emphasis <= (debug_dr_swing * err) + (13'(carrier_period_deemphasis) <<< 4); 101 | end 102 | end 103 | 104 | // calculate the phase accumulator. perform frequency modulation 105 | always_ff @(posedge clk) begin 106 | if (even_line) begin 107 | phase_increment_ampl <= `SECAM_CHROMA_DB_DDS_INCREMENT + (51'(carrier_period_emphasis)<<<35); 108 | phase_increment <= `SECAM_CHROMA_DB_DDS_INCREMENT + (51'(carrier_period_emphasis_delayed)<<<35); 109 | end else begin 110 | phase_increment_ampl <= `SECAM_CHROMA_DR_DDS_INCREMENT - (51'(carrier_period_emphasis)<<<35); 111 | phase_increment <= `SECAM_CHROMA_DR_DDS_INCREMENT - (51'(carrier_period_emphasis_delayed)<<<35); 112 | end 113 | end 114 | 115 | always_ff @(posedge clk) begin 116 | clockdivide_counter <= clockdivide_counter + phase_increment; 117 | end 118 | 119 | 120 | sinus sinus0 ( 121 | .clk(clk), 122 | .phase(carrier_phase), 123 | .amplitude(carrier_amplitude), 124 | .out(chroma) 125 | ); 126 | 127 | 128 | 129 | `ifdef VERILATOR 130 | bit signed [9:0] carrier_period_filtered_check; 131 | bit signed [9:0] carrier_period_filtered_check_q; 132 | bit signed [8:0] carrier_period_filtered_check_q2; 133 | bit signed [9:0] carrier_period_emphasis_check; 134 | bit signed [9:0] carrier_period_emphasis_check_q; 135 | bit signed [8:0] carrier_period_emphasis_check_q2; 136 | 137 | bit [5:0] enabled_amplitude_filtered_check; 138 | bit [5:0] enabled_amplitude_filtered_check_q2; 139 | 140 | // verilator lint_off WIDTHEXPAND 141 | 142 | filter_int_5tap amplow_check ( 143 | .clk(clk), 144 | .in(enabled_amplitude), 145 | .out(enabled_amplitude_filtered_check), 146 | .b0(`SECAM_AMPLITUDE_LOWPASS_B0), 147 | .b1(`SECAM_AMPLITUDE_LOWPASS_B1), 148 | .b2(`SECAM_AMPLITUDE_LOWPASS_B2), 149 | .b3(0), 150 | .b4(0), 151 | .a1(`SECAM_AMPLITUDE_LOWPASS_A1), 152 | .a2(`SECAM_AMPLITUDE_LOWPASS_A2), 153 | .a3(0), 154 | .a4(0), 155 | .a_precision(`SECAM_AMPLITUDE_LOWPASS_A_AFTER_DOT), 156 | .b_precision(`SECAM_AMPLITUDE_LOWPASS_B_AFTER_DOT) 157 | ); 158 | 159 | filter_int_5tap carrier_lowpass_check ( 160 | .clk(clk), 161 | .in(carrier_period_modulate <<< 1), 162 | .out(carrier_period_filtered_check), 163 | .b0(`SECAM_CHROMA_LOWPASS_B0), 164 | .b1(`SECAM_CHROMA_LOWPASS_B1), 165 | .b2(`SECAM_CHROMA_LOWPASS_B2), 166 | .b3(0), 167 | .b4(0), 168 | .a1(`SECAM_CHROMA_LOWPASS_A1), 169 | .a2(`SECAM_CHROMA_LOWPASS_A2), 170 | .a3(0), 171 | .a4(0), 172 | .a_precision(`SECAM_CHROMA_LOWPASS_A_AFTER_DOT), 173 | .b_precision(`SECAM_CHROMA_LOWPASS_B_AFTER_DOT) 174 | ); 175 | 176 | 177 | filter_int_5tap_floorA deemphasis_check ( 178 | .clk(clk), 179 | .in((carrier_period_emphasis >>> 4) <<< 1), 180 | .out(carrier_period_emphasis_check), 181 | .b0(`SECAM_PREEMPHASIS_B0), 182 | .b1(`SECAM_PREEMPHASIS_B1), 183 | .b2(`SECAM_PREEMPHASIS_B2), 184 | .b3(0), 185 | .b4(0), 186 | .a1(`SECAM_PREEMPHASIS_A1), 187 | .a2(`SECAM_PREEMPHASIS_A2), 188 | .a3(0), 189 | .a4(0), 190 | .a_precision(`SECAM_PREEMPHASIS_A_AFTER_DOT), 191 | .b_precision(`SECAM_PREEMPHASIS_B_AFTER_DOT) 192 | ); 193 | // verilator lint_on WIDTHEXPAND 194 | 195 | 196 | bit failed = 0; 197 | 198 | always_ff @(posedge clk) begin 199 | carrier_period_filtered_check_q <= carrier_period_filtered_check; 200 | carrier_period_filtered_check_q2 <= carrier_period_filtered_check_q[9:1]; 201 | 202 | carrier_period_emphasis_check_q <= carrier_period_emphasis_check; 203 | carrier_period_emphasis_check_q2 <= carrier_period_emphasis_check_q[9:1]; 204 | 205 | enabled_amplitude_filtered_check_q2 <= enabled_amplitude_filtered_check; 206 | 207 | if (carrier_period_filtered != carrier_period_filtered_check_q2) failed <= 1; 208 | assert (carrier_period_filtered == carrier_period_filtered_check_q2); 209 | 210 | if (carrier_period_deemphasis != carrier_period_emphasis_check_q2) failed <= 1; 211 | assert (carrier_period_deemphasis == carrier_period_emphasis_check_q2); 212 | 213 | if (enabled_amplitude_filtered != enabled_amplitude_filtered_check_q2) failed <= 1; 214 | assert (enabled_amplitude_filtered == enabled_amplitude_filtered_check_q2); 215 | end 216 | 217 | `endif 218 | 219 | 220 | endmodule 221 | 222 | -------------------------------------------------------------------------------- /rtl/sinus.sv: -------------------------------------------------------------------------------- 1 | /* Sine wave generator 2 | * Provides samples of a sine wave with provided amplitude. 3 | * The phase is expected to be fed from a DDS phase accumulator. 4 | * The synthesis tool is expected to use a bram instance to implement this. 5 | */ 6 | 7 | module sinus ( 8 | input clk, 9 | input [4:0] phase, // phase from 0 to 31 in steps of 11.25 (360/32) degrees 10 | input signed [5:0] amplitude, // amplitudes in range -31 to 31 11 | output bit signed [7:0] out 12 | ); 13 | bit [5:0] amplitude_index; 14 | bit [4:0] phase_internal; 15 | wire [10:0] index = {amplitude_index, phase_internal}; 16 | 17 | bit signed [7:0] lut[2048]; 18 | 19 | bit [4:0] phase_q; 20 | bit signed [5:0] amplitude_q; 21 | 22 | initial begin 23 | $readmemh("../mem/sinewave.txt", lut); 24 | end 25 | 26 | always_comb begin 27 | if (amplitude_q < 0) begin 28 | amplitude_index = -amplitude_q; 29 | phase_internal = phase_q + 16; 30 | end else begin 31 | amplitude_index = amplitude_q; 32 | phase_internal = phase_q; 33 | end 34 | end 35 | 36 | always_ff @(posedge clk) begin 37 | amplitude_q <= amplitude; 38 | phase_q <= phase; 39 | 40 | out <= lut[index]; 41 | end 42 | 43 | endmodule 44 | -------------------------------------------------------------------------------- /rtl/top_dactest_frequency.sv: -------------------------------------------------------------------------------- 1 | 2 | module top_dactest_frequency ( 3 | input clk27, 4 | output uart_tx, 5 | input uart_rx, 6 | input rst_n, 7 | input switch1, 8 | output bit [7:0] video, 9 | output bit [7:6] video_extra 10 | ); 11 | 12 | Gowin_rPLL clk27to48 ( 13 | .clkin (clk27), //input clkin 14 | .clkout(clkout_o) //output clkout 15 | ); 16 | 17 | localparam int ClockDivideLastBit = 50; 18 | 19 | bit [ClockDivideLastBit:0] clockdivide_counter = 0; 20 | 21 | bit [4:0] carrier_phase = 0; 22 | wire signed [7:0] sinus_out; 23 | sinus sin0 ( 24 | .clk(clkout_o), 25 | .phase(carrier_phase), 26 | .amplitude(12), 27 | .out(sinus_out) 28 | ); 29 | 30 | bit [7:0] out = 0; 31 | 32 | always_comb begin 33 | out = 100 + sinus_out; 34 | end 35 | 36 | bit [8:0] cnt = 0; 37 | 38 | always_ff @(posedge clkout_o) begin 39 | cnt <= cnt + 1; 40 | 41 | //carrier_phase <= cnt[4:0]; 42 | 43 | clockdivide_counter <= clockdivide_counter + 51'd207992122400030; 44 | carrier_phase <= clockdivide_counter[ClockDivideLastBit:ClockDivideLastBit-4]; 45 | 46 | video <= out; 47 | video_extra[7:6] <= out[7:6]; 48 | end 49 | 50 | endmodule 51 | -------------------------------------------------------------------------------- /rtl/top_dactest_sawtooth.sv: -------------------------------------------------------------------------------- 1 | 2 | module top_dactest_sawtooth ( 3 | input clk27, 4 | output uart_tx, 5 | input uart_rx, 6 | input rst_n, 7 | output bit [7:0] video, 8 | output bit [7:6] video_extra 9 | ); 10 | 11 | bit [31:0] dac_counter = 0; 12 | bit [31:0] out = 0; 13 | 14 | always_comb begin 15 | out = dac_counter[10:10-7]; 16 | end 17 | 18 | always_ff @(posedge clk27) begin 19 | dac_counter <= dac_counter + 1; 20 | 21 | video <= out; 22 | video_extra[7:6] <= out[7:6]; 23 | // video <=8'hff; 24 | end 25 | 26 | endmodule 27 | -------------------------------------------------------------------------------- /rtl/top_testpic_generator.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | `include "common.svh" 3 | 4 | import common::*; 5 | 6 | module top_testpic_generator ( 7 | input clk27, 8 | input switch1, 9 | input sys_resetn, 10 | output bit [7:0] video, 11 | output bit [7:6] video_extra, 12 | input uart_rx, 13 | output uart_tx, 14 | output bit [5:0] led, 15 | 16 | output [ 1:0] O_psram_ck, // Magic ports for PSRAM to be inferred 17 | output [ 1:0] O_psram_ck_n, 18 | inout [ 1:0] IO_psram_rwds, 19 | inout [15:0] IO_psram_dq, 20 | output [ 1:0] O_psram_reset_n, 21 | output [ 1:0] O_psram_cs_n 22 | ) /* synthesis syn_netlist_hierarchy=0 */; 23 | 24 | wire video_overflow; 25 | 26 | assign video_extra[7:6] = video[7:6]; 27 | assign led[5:1] = 5'b11111; 28 | assign led[0] = !video_overflow; 29 | 30 | wire lock_o; 31 | wire clk96; 32 | wire clk96_p; 33 | wire clkoutd_o; 34 | wire clk /*verilator public_flat_rw*/; 35 | 36 | `ifndef VERILATOR 37 | Gowin_rPLL pll ( 38 | .clkout(clk96), //output clkout 39 | .lock(lock_o), //output lock 40 | .clkoutp(clk96_p), //output clkoutp 41 | .clkoutd(clkoutd_o), //output clkoutd 42 | .clkin(clk27) //input clkin 43 | ); 44 | `endif 45 | 46 | burst_bus_if mem_bus (clk); 47 | burst_bus_if debug_mem_bus (clk); 48 | 49 | wire calib; 50 | 51 | PSRAM_Memory_Interface_HS_V2_Top u_psram_top ( 52 | .clk_d(clkoutd_o), //input clk_d 53 | .memory_clk(clk96), //input memory_clk 54 | .memory_clk_p(clk96_p), //input memory_clk_p 55 | .pll_lock(lock_o), //input pll_lock 56 | .rst_n(1'b1), //input rst_n 57 | .O_psram_ck(O_psram_ck), //output [1:0] O_psram_ck 58 | .O_psram_ck_n(O_psram_ck_n), //output [1:0] O_psram_ck_n 59 | .IO_psram_dq(IO_psram_dq), //inout [15:0] IO_psram_dq 60 | .IO_psram_rwds(IO_psram_rwds), //inout [1:0] IO_psram_rwds 61 | .O_psram_cs_n(O_psram_cs_n), //output [1:0] O_psram_cs_n 62 | .O_psram_reset_n(O_psram_reset_n), //output [1:0] O_psram_reset_n 63 | .wr_data(mem_bus.wr_data), //input [63:0] wr_data 64 | .rd_data(mem_bus.rd_data), //output [63:0] rd_data 65 | .rd_data_valid(mem_bus.rd_data_valid), //output rd_data_valid 66 | .addr(mem_bus.addr), //input [20:0] addr 67 | .cmd(mem_bus.cmd), //input cmd 68 | .cmd_en(mem_bus.cmd_en), //input cmd_en 69 | .init_calib(calib), //output init_calib 70 | .clk_out(clk), //output clk_out 71 | .data_mask(mem_bus.data_mask) //input [7:0] data_mask 72 | ); 73 | 74 | bit [5:0] cycle = 0; // 14 cycles between write and read 75 | bit busy = 0; 76 | wire ram_ready = !busy && calib; 77 | assign mem_bus.ready = ram_ready; 78 | 79 | // implement busy flag as the memory controller doesn't create such a signal 80 | always_ff @(posedge clk) begin 81 | if (mem_bus.cmd_en) begin 82 | busy <= 1; 83 | cycle <= 0; 84 | end 85 | 86 | if (busy) begin 87 | cycle <= cycle + 1; 88 | 89 | // IPUG 943 - Table 4-2, Tcmd is 14 when burst==16 90 | /* This is a weird thing. Usually after 14 cycles the RAM controller 91 | * is again able to take a command. But the data which is read back 92 | * for the current read command is delivered after about 14 cycles as well. 93 | * This makes it a little bit more difficult for multiple masters 94 | * as communication is interleaved. 95 | * As I currently don't want to solve this, I set this value to 20 96 | * instead of the probably more performant 13. 97 | */ 98 | if (cycle == 20) begin 99 | busy <= 0; 100 | end 101 | end 102 | end 103 | 104 | 105 | debug_bus_if dbus (clk); 106 | 107 | uart_busmaster uart_db ( 108 | .clk, 109 | .uart_rx, 110 | .uart_tx, 111 | .dbus(dbus.master) 112 | ); 113 | 114 | localparam bit [8:0] NumberOfLines_50HZ = 312; 115 | localparam bit [8:0] NumberOfLines_60Hz = 262; 116 | localparam bit [8:0] NumberOfVisibleLines_50HZ = 256; 117 | localparam bit [8:0] NumberOfVisibleLines_60HZ = 200; 118 | 119 | bit sync; 120 | bit newline; 121 | bit newframe; 122 | bit newpixel; 123 | bit qam_startburst; 124 | bit [8:0] video_y; 125 | bit [12:0] video_x; 126 | bit visible_line; 127 | bit visible_window; 128 | bit [8:0] v_total = NumberOfLines_50HZ; 129 | bit [8:0] v_active = NumberOfVisibleLines_50HZ; 130 | bit even_field; 131 | bit interlacing_enable; 132 | 133 | video_timing video_timing0 ( 134 | .clk(clk), 135 | .v_total(v_total), 136 | .v_active(v_active), 137 | .interlacing_enable, 138 | .sync(sync), 139 | .newline(newline), 140 | .newframe(newframe), 141 | .newpixel(newpixel), 142 | .startburst(qam_startburst), 143 | .video_x(video_x), 144 | .video_y(video_y), 145 | 146 | .visible_line (visible_line), 147 | .visible_window(visible_window), 148 | .even_field 149 | ); 150 | 151 | video_standard_e video_standard = PAL; 152 | ycbcr_t cvbs_in; 153 | 154 | bit secam_enabled; 155 | 156 | composite_video_encoder cvbs ( 157 | .clk, 158 | .sync(sync), 159 | .newframe, 160 | .newline, 161 | .secam_enabled, 162 | .qam_startburst, 163 | .video_standard, 164 | .in (cvbs_in), 165 | .video, 166 | .video_overflow, 167 | .dbus 168 | ); 169 | burst_bus_if fb_bus (clk); 170 | 171 | burst_bus_arbiter arbiter ( 172 | .mem(mem_bus), 173 | .m2 (debug_mem_bus), 174 | .m1 (fb_bus) 175 | ); 176 | 177 | ycbcr_t fb_output; 178 | 179 | 180 | framebuffer fb ( 181 | .bus(fb_bus), 182 | .newframe, 183 | .newline, 184 | .even_field, 185 | .video_y, 186 | .video_x, 187 | .out(fb_output), 188 | .dbus 189 | ); 190 | 191 | ycbcr_t colorbars_output; 192 | bit colorbars_active = 1; 193 | 194 | rgbbars testpattern ( 195 | .clk, 196 | .newline, 197 | .newpixel, 198 | .video_y(8'(video_y - 9'(38))), 199 | .visible_window, 200 | .out(colorbars_output) 201 | ); 202 | 203 | always_ff @(posedge clk) begin 204 | if (dbus.addr[15:8] == 8'h00 && dbus.write_enable) begin 205 | case (dbus.addr[7:0]) 206 | 1: video_standard <= video_standard_e'(dbus.write_data[1:0]); 207 | 2: v_total[8] <= dbus.write_data[0]; 208 | 3: v_total[7:0] <= dbus.write_data; 209 | 4: v_active[8] <= dbus.write_data[0]; 210 | 5: v_active[7:0] <= dbus.write_data; 211 | 6: begin 212 | colorbars_active <= dbus.write_data[3]; 213 | interlacing_enable <= dbus.write_data[5]; 214 | end 215 | default: ; 216 | endcase 217 | end 218 | end 219 | 220 | wire [7:0] bw_data = dbus.write_data; 221 | wire bw_strobe = dbus.addr == 10 && dbus.write_enable; 222 | wire bw_reset = dbus.addr == 11 && dbus.write_enable; 223 | assign dbus.ready = 1; 224 | 225 | burst_writer bw ( 226 | .reset(bw_reset), 227 | .data(bw_data), 228 | .strobe(bw_strobe), 229 | .mem(debug_mem_bus) 230 | ); 231 | 232 | always_comb begin 233 | secam_enabled = 0; 234 | if (video_y > 7) begin 235 | secam_enabled = 1; 236 | end 237 | 238 | cvbs_in = fb_output; 239 | if (colorbars_active) begin 240 | cvbs_in = colorbars_output; 241 | end 242 | end 243 | endmodule 244 | -------------------------------------------------------------------------------- /rtl/uart_busmaster.sv: -------------------------------------------------------------------------------- 1 | module uart_busmaster ( 2 | input clk, 3 | input uart_rx, 4 | output uart_tx, 5 | 6 | debug_bus_if.master dbus 7 | ); 8 | 9 | bit [7:0] o_com_data /* verilator public_flat_rw */; 10 | bit [7:0] i_com_data /* verilator public_flat_rw */; 11 | bit i_com_strobe /* verilator public_flat_rw */; 12 | bit o_com_strobe /* verilator public_flat_rw */; 13 | 14 | `ifndef MODEL_TECH 15 | `ifndef VERILATOR 16 | uart_rx #( 17 | .CLK_FRE (48), 18 | .BAUD_RATE(3000000) 19 | ) uart_rx_inst ( 20 | .clk (clk), 21 | .rst_n (1'b1), 22 | .rx_data (i_com_data), 23 | .rx_data_valid(i_com_strobe), 24 | .rx_data_ready(1'b1), 25 | .rx_pin (uart_rx) 26 | ); 27 | 28 | uart_tx #( 29 | .CLK_FRE (48), 30 | .BAUD_RATE(3000000) 31 | ) uart_tx_inst ( 32 | .clk (clk), 33 | .rst_n (1'b1), 34 | .tx_data (o_com_data), 35 | .tx_data_valid(o_com_strobe), 36 | .tx_data_ready(), 37 | .tx_pin (uart_tx) 38 | ); 39 | `endif 40 | `endif 41 | 42 | debug_busmaster db ( 43 | .clk, 44 | .i_com_data (i_com_data), 45 | .i_com_strobe(i_com_strobe), 46 | .o_com_data (o_com_data), 47 | .o_com_strobe(o_com_strobe), 48 | 49 | .dbus 50 | ); 51 | 52 | endmodule 53 | -------------------------------------------------------------------------------- /rtl/uart_rx.v: -------------------------------------------------------------------------------- 1 | // From https://github.com/sipeed/TangNano-9K-example/blob/main/uart/src/uart_rx.v 2 | // verilator lint_off WIDTHEXPAND 3 | 4 | module uart_rx #( 5 | parameter CLK_FRE = 50, //clock frequency(Mhz) 6 | parameter BAUD_RATE = 115200 //serial baud rate 7 | ) ( 8 | input clk, //clock input 9 | input rst_n, //asynchronous reset input, low active 10 | output reg [7:0] rx_data, //received serial data 11 | output reg rx_data_valid, //received serial data is valid 12 | input rx_data_ready, //data receiver module ready 13 | input rx_pin //serial data input 14 | ); 15 | //calculates the clock cycle for baud rate 16 | localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; 17 | //state machine code 18 | localparam S_IDLE = 1; 19 | localparam S_START = 2; //start bit 20 | localparam S_REC_BYTE = 3; //data bits 21 | localparam S_STOP = 4; //stop bit 22 | localparam S_DATA = 5; 23 | 24 | reg [ 2:0] state; 25 | reg [ 2:0] next_state; 26 | reg rx_d0; //delay 1 clock for rx_pin 27 | reg rx_d1; //delay 1 clock for rx_d0 28 | wire rx_negedge; //negedge of rx_pin 29 | reg [ 7:0] rx_bits; //temporary storage of received data 30 | reg [15:0] cycle_cnt; //baud counter 31 | reg [ 2:0] bit_cnt; //bit counter 32 | 33 | assign rx_negedge = rx_d1 && ~rx_d0; 34 | 35 | always @(posedge clk or negedge rst_n) begin 36 | if (rst_n == 1'b0) begin 37 | rx_d0 <= 1'b0; 38 | rx_d1 <= 1'b0; 39 | end else begin 40 | rx_d0 <= rx_pin; 41 | rx_d1 <= rx_d0; 42 | end 43 | end 44 | 45 | 46 | always @(posedge clk or negedge rst_n) begin 47 | if (rst_n == 1'b0) state <= S_IDLE; 48 | else state <= next_state; 49 | end 50 | 51 | always @(*) begin 52 | case (state) 53 | S_IDLE: 54 | if (rx_negedge) next_state = S_START; 55 | else next_state = S_IDLE; 56 | S_START: 57 | if (cycle_cnt == CYCLE - 1) //one data cycle 58 | next_state = S_REC_BYTE; 59 | else next_state = S_START; 60 | S_REC_BYTE: 61 | if (cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) //receive 8bit data 62 | next_state = S_STOP; 63 | else next_state = S_REC_BYTE; 64 | S_STOP: 65 | if (cycle_cnt == CYCLE / 2 - 1) //half bit cycle,to avoid missing the next byte receiver 66 | next_state = S_DATA; 67 | else next_state = S_STOP; 68 | S_DATA: 69 | if (rx_data_ready) //data receive complete 70 | next_state = S_IDLE; 71 | else next_state = S_DATA; 72 | default: next_state = S_IDLE; 73 | endcase 74 | end 75 | 76 | always @(posedge clk or negedge rst_n) begin 77 | if (rst_n == 1'b0) rx_data_valid <= 1'b0; 78 | else if (state == S_STOP && next_state != state) rx_data_valid <= 1'b1; 79 | else if (state == S_DATA && rx_data_ready) rx_data_valid <= 1'b0; 80 | end 81 | 82 | always @(posedge clk or negedge rst_n) begin 83 | if (rst_n == 1'b0) rx_data <= 8'd0; 84 | else if (state == S_STOP && next_state != state) rx_data <= rx_bits; //latch received data 85 | end 86 | 87 | always @(posedge clk or negedge rst_n) begin 88 | if (rst_n == 1'b0) begin 89 | bit_cnt <= 3'd0; 90 | end else if (state == S_REC_BYTE) 91 | if (cycle_cnt == CYCLE - 1) bit_cnt <= bit_cnt + 3'd1; 92 | else bit_cnt <= bit_cnt; 93 | else bit_cnt <= 3'd0; 94 | end 95 | 96 | 97 | always @(posedge clk or negedge rst_n) begin 98 | if (rst_n == 1'b0) cycle_cnt <= 16'd0; 99 | else if ((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) 100 | cycle_cnt <= 16'd0; 101 | else cycle_cnt <= cycle_cnt + 16'd1; 102 | end 103 | //receive serial data bit data 104 | always @(posedge clk or negedge rst_n) begin 105 | if (rst_n == 1'b0) rx_bits <= 8'd0; 106 | else if (state == S_REC_BYTE && cycle_cnt == CYCLE / 2 - 1) rx_bits[bit_cnt] <= rx_pin; 107 | else rx_bits <= rx_bits; 108 | end 109 | endmodule 110 | -------------------------------------------------------------------------------- /rtl/uart_tx.v: -------------------------------------------------------------------------------- 1 | // From https://github.com/sipeed/TangNano-9K-example/blob/main/uart/src/uart_tx.v 2 | // verilator lint_off WIDTHEXPAND 3 | 4 | module uart_tx #( 5 | parameter CLK_FRE = 50, //clock frequency(Mhz) 6 | parameter BAUD_RATE = 115200 //serial baud rate 7 | ) ( 8 | input clk, //clock input 9 | input rst_n, //asynchronous reset input, low active 10 | input [7:0] tx_data, //data to send 11 | input tx_data_valid, //data to be sent is valid 12 | output reg tx_data_ready, //send ready 13 | output tx_pin //serial data output 14 | ); 15 | //calculates the clock cycle for baud rate 16 | localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; 17 | //state machine code 18 | localparam S_IDLE = 1; 19 | localparam S_START = 2; //start bit 20 | localparam S_SEND_BYTE = 3; //data bits 21 | localparam S_STOP = 4; //stop bit 22 | reg [ 2:0] state; 23 | reg [ 2:0] next_state; 24 | reg [15:0] cycle_cnt; //baud counter 25 | reg [ 2:0] bit_cnt; //bit counter 26 | reg [ 7:0] tx_data_latch; //latch data to send 27 | reg tx_reg; //serial data output 28 | assign tx_pin = tx_reg; 29 | always @(posedge clk or negedge rst_n) begin 30 | if (rst_n == 1'b0) state <= S_IDLE; 31 | else state <= next_state; 32 | end 33 | 34 | always @(*) begin 35 | case (state) 36 | S_IDLE: 37 | if (tx_data_valid == 1'b1) next_state = S_START; 38 | else next_state = S_IDLE; 39 | S_START: 40 | if (cycle_cnt == CYCLE - 1) next_state = S_SEND_BYTE; 41 | else next_state = S_START; 42 | S_SEND_BYTE: 43 | if (cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) next_state = S_STOP; 44 | else next_state = S_SEND_BYTE; 45 | S_STOP: 46 | if (cycle_cnt == CYCLE - 1) next_state = S_IDLE; 47 | else next_state = S_STOP; 48 | default: next_state = S_IDLE; 49 | endcase 50 | end 51 | always @(posedge clk or negedge rst_n) begin 52 | if (rst_n == 1'b0) begin 53 | tx_data_ready <= 1'b0; 54 | end else if (state == S_IDLE) 55 | if (tx_data_valid == 1'b1) tx_data_ready <= 1'b0; 56 | else tx_data_ready <= 1'b1; 57 | else if (state == S_STOP && cycle_cnt == CYCLE - 1) tx_data_ready <= 1'b1; 58 | end 59 | 60 | 61 | always @(posedge clk or negedge rst_n) begin 62 | if (rst_n == 1'b0) begin 63 | tx_data_latch <= 8'd0; 64 | end else if (state == S_IDLE && tx_data_valid == 1'b1) tx_data_latch <= tx_data; 65 | 66 | end 67 | 68 | always @(posedge clk or negedge rst_n) begin 69 | if (rst_n == 1'b0) begin 70 | bit_cnt <= 3'd0; 71 | end else if (state == S_SEND_BYTE) 72 | if (cycle_cnt == CYCLE - 1) bit_cnt <= bit_cnt + 3'd1; 73 | else bit_cnt <= bit_cnt; 74 | else bit_cnt <= 3'd0; 75 | end 76 | 77 | 78 | always @(posedge clk or negedge rst_n) begin 79 | if (rst_n == 1'b0) cycle_cnt <= 16'd0; 80 | else if ((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) 81 | cycle_cnt <= 16'd0; 82 | else cycle_cnt <= cycle_cnt + 16'd1; 83 | end 84 | 85 | always @(posedge clk or negedge rst_n) begin 86 | if (rst_n == 1'b0) tx_reg <= 1'b1; 87 | else 88 | case (state) 89 | S_IDLE, S_STOP: tx_reg <= 1'b1; 90 | S_START: tx_reg <= 1'b0; 91 | S_SEND_BYTE: tx_reg <= tx_data_latch[bit_cnt]; 92 | default: tx_reg <= 1'b1; 93 | endcase 94 | end 95 | 96 | endmodule 97 | -------------------------------------------------------------------------------- /rtl/video_timing.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | /* 4 | * Produces TV video timing and fitting sync signals. 5 | * 6 | * For PAL we have 625 lines in interlacing mode 7 | * Field 1 = Even Lines = 312 lines 8 | * Field 2 = Odd Lines = 313 lines 9 | * For non interlaced mode, it seems to be defined to use 312 lines. 10 | * Therefore we can implement non interlaced modes by just providing only even frames. 11 | */ 12 | module video_timing ( 13 | input clk, 14 | input [8:0] v_total, 15 | input [8:0] v_active, 16 | input interlacing_enable, 17 | 18 | output bit sync, 19 | 20 | output bit newline, 21 | output bit newframe, 22 | output bit newpixel, 23 | 24 | output bit startburst, 25 | output bit [8:0] video_y, 26 | output bit [12:0] video_x, 27 | 28 | output bit visible_line, 29 | output visible_window, 30 | output bit even_field 31 | ); 32 | // Internal bits for the phase accumulator 33 | localparam int PixelPhaseAccu = 10; 34 | 35 | // We require one more bit for disabling the active window when 36 | // we heave reached pixel 256 which shall not be shown 37 | bit [PixelPhaseAccu-1:0] pixel_clock_accu = 0; 38 | bit [PixelPhaseAccu-1:0] pixel_counter_increment = 0; 39 | bit pixel_clock_accu_highest = 0; 40 | 41 | bit sync_d; 42 | 43 | // Length of certain timing constants in clocks 44 | localparam bit [12:0] LineLength = 13'(integer'(64 / `CLK_PERIOD_USEC)); // 64 usec 45 | localparam bit [12:0] HalfLineLength = LineLength / 2; // 32 usec 46 | 47 | localparam bit [12:0] BackPorch = 13'(integer'(10 / `CLK_PERIOD_USEC)); // 10 usec 48 | localparam bit [12:0] FrontPorch = 13'(integer'(1.65 / `CLK_PERIOD_USEC)); // 1.65 usec 49 | 50 | localparam bit [12:0] NormalSync = 13'(integer'(4.7 / `CLK_PERIOD_USEC)); // 4.7 usec 51 | localparam bit [12:0] ShortSync = NormalSync / 2; 52 | localparam bit [12:0] LongSync = HalfLineLength - NormalSync; 53 | 54 | localparam bit [8:0] VisibleStartY = 38; 55 | 56 | localparam bit [12:0] BurstStart = 13'(integer'(5.6 / `CLK_PERIOD_USEC)); // 5.6 usec 57 | 58 | localparam bit [12:0] ActiveWindowStart = NormalSync + BackPorch; 59 | // TODO +4 is used here because the resolution of PixelPhaseAccu is too limited and 60 | // we want to have an equal width for all "pixels". Need to find a better solution. 61 | localparam bit [12:0] ActiveWindowStop = LineLength - 13'(integer'(5 / `CLK_PERIOD_USEC))+4; // 64 usec 62 | 63 | initial begin 64 | // pixel_counter_increment * ticks_per_active_window must about reach a 65 | // pixel_x_internal value of having only the highest bit set 66 | automatic 67 | int 68 | ticks_per_active_window = integer'(ActiveWindowStop) - integer'(ActiveWindowStart); 69 | pixel_counter_increment = PixelPhaseAccu'((2 ** PixelPhaseAccu)*256 / ticks_per_active_window); 70 | even_field = 1; 71 | end 72 | 73 | always_ff @(posedge clk) begin 74 | if (video_x == (LineLength - 1)) begin // end of line reached? 75 | video_x <= 0; 76 | pixel_clock_accu <= 0; 77 | 78 | if (even_field && video_y == (v_total - 1)) begin 79 | video_y <= 0; 80 | // Only change to odd field if interlacing mode is active 81 | if (interlacing_enable) even_field <= 0; 82 | end else if (!even_field && video_y == (v_total)) begin 83 | video_y <= 0; 84 | even_field <= 1; 85 | end else video_y <= video_y + 1; 86 | end else begin 87 | video_x <= video_x + 1; 88 | 89 | if (visible_window) pixel_clock_accu <= pixel_clock_accu + pixel_counter_increment; 90 | end 91 | 92 | sync <= sync_d; 93 | pixel_clock_accu_highest <= pixel_clock_accu[PixelPhaseAccu-1]; 94 | end 95 | 96 | // Calculate sync signal and burst position 97 | 98 | bit first_half_long_sync; 99 | bit second_half_long_sync; 100 | bit timing_line; 101 | bit visible_window_q = 0; 102 | bit visible_window_d; 103 | always_ff @(posedge clk) begin 104 | newframe <= (video_y == 0 && video_x == 0); 105 | newline <= (video_x == 0); 106 | visible_window_q <= visible_window_d; 107 | end 108 | 109 | assign visible_window = visible_window_q; 110 | 111 | always_comb begin 112 | sync_d = 0; 113 | startburst = 0; 114 | visible_line = 0; 115 | visible_window_d = 0; 116 | newpixel = !pixel_clock_accu_highest && pixel_clock_accu[PixelPhaseAccu-1]; 117 | 118 | first_half_long_sync = 0; 119 | second_half_long_sync = 0; 120 | timing_line = 0; 121 | 122 | timing_line = (video_y <= 4) || (video_y >= 310); 123 | 124 | if (even_field) begin 125 | // First field draws even lines 126 | first_half_long_sync = (video_y == 0 || video_y == 1 || video_y == 2); 127 | second_half_long_sync = (video_y == 0 || video_y == 1); 128 | end else begin 129 | // Second field draws odd lines 130 | first_half_long_sync = (video_y == 1 || video_y == 2); 131 | second_half_long_sync = (video_y == 0 || video_y == 1 || video_y == 2); 132 | end 133 | 134 | if (timing_line) begin 135 | if (first_half_long_sync && second_half_long_sync) begin 136 | // ______-______- 137 | if (video_x < LongSync) sync_d = 1; 138 | else if (video_x < HalfLineLength) sync_d = 0; 139 | else if (video_x < (HalfLineLength + LongSync)) sync_d = 1; 140 | end 141 | 142 | if (first_half_long_sync && !second_half_long_sync) begin 143 | // ______-_------ for first and second line in even fields 144 | if (video_x < LongSync) sync_d = 1; 145 | else if (video_x < HalfLineLength) sync_d = 0; 146 | else if (video_x < (HalfLineLength + ShortSync)) sync_d = 1; 147 | end 148 | 149 | if (!first_half_long_sync && second_half_long_sync) begin 150 | // _------______- used only for field 2 in the first line 151 | if (video_x < ShortSync) sync_d = 1; 152 | else if (video_x < HalfLineLength) sync_d = 0; 153 | else if (video_x < (HalfLineLength + LongSync)) sync_d = 1; 154 | end 155 | 156 | if (!first_half_long_sync && !second_half_long_sync) begin 157 | // _------_------ 158 | if (video_x < ShortSync) sync_d = 1; 159 | else if (video_x < HalfLineLength) sync_d = 0; 160 | else if (video_x < (HalfLineLength + ShortSync)) sync_d = 1; 161 | end 162 | end else begin 163 | // 256 visible lines starting at line 38 164 | if (video_y >= VisibleStartY && video_y < (VisibleStartY + v_active)) begin 165 | visible_line = 1; 166 | if (video_x >= ActiveWindowStart && video_x <= ActiveWindowStop) 167 | visible_window_d = 1; 168 | end 169 | if (video_x < NormalSync) sync_d = 1; 170 | if (video_x == BurstStart && video_y > 7) startburst = 1; 171 | end 172 | end 173 | 174 | 175 | `ifdef VERILATOR 176 | int field_line_number; 177 | // verilator lint_off WIDTHEXPAND 178 | always_comb begin 179 | field_line_number = video_y + 1 + (!even_field ? 312 : 0); 180 | end 181 | // verilator lint_on WIDTHEXPAND 182 | `endif 183 | 184 | endmodule 185 | -------------------------------------------------------------------------------- /sim/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | obj_dir 3 | 4 | /waveform.vcd 5 | /derp.png 6 | /working.png 7 | -------------------------------------------------------------------------------- /sim/convolver.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.116 (w)1999-2023 BSI 3 | [*] Thu Jan 25 18:06:07 2024 4 | [*] 5 | [dumpfile] "/home/andre/GIT/fpga_pong/sim/waveform.vcd" 6 | [dumpfile_mtime] "Thu Jan 25 18:00:26 2024" 7 | [dumpfile_size] 232787416 8 | [savefile] "/home/andre/GIT/fpga_pong/sim/convolver.gtkw" 9 | [timestart] 184305 10 | [size] 1920 1152 11 | [pos] -1 -1 12 | *-5.277835 184457 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] TOP. 14 | [treeopen] TOP.top_testpic_generator. 15 | [treeopen] TOP.top_testpic_generator.fb. 16 | [sst_width] 315 17 | [signals_width] 378 18 | [sst_expanded] 1 19 | [sst_vpaned_height] 347 20 | @22 21 | TOP.top_testpic_generator.fb.bus.addr[20:0] 22 | @28 23 | TOP.top_testpic_generator.fb.bus.clk 24 | TOP.top_testpic_generator.fb.bus.cmd 25 | TOP.top_testpic_generator.fb.bus.cmd_en 26 | @22 27 | TOP.top_testpic_generator.fb.bus.data_mask[7:0] 28 | TOP.top_testpic_generator.fb.bus.rd_data[63:0] 29 | @28 30 | TOP.top_testpic_generator.fb.bus.rd_data_valid 31 | TOP.top_testpic_generator.fb.bus.ready 32 | @22 33 | TOP.top_testpic_generator.fb.bus.wr_data[63:0] 34 | TOP.top_testpic_generator.fb.RegisterHighAddr[7:0] 35 | @28 36 | TOP.top_testpic_generator.fb.clk 37 | @22 38 | TOP.top_testpic_generator.fb.clk_per_pixel[5:0] 39 | TOP.top_testpic_generator.fb.convolver_in32[31:0] 40 | @28 41 | TOP.top_testpic_generator.fb.convolver_input_valid 42 | @22 43 | TOP.top_testpic_generator.fb.convolver_out24[23:0] 44 | @28 45 | TOP.top_testpic_generator.fb.convolver_out24_ready 46 | @29 47 | TOP.top_testpic_generator.fb.convolver_strobe_input 48 | @28 49 | TOP.top_testpic_generator.fb.convolver_strobe_out24 50 | TOP.top_testpic_generator.fb.debug_line_mode 51 | TOP.top_testpic_generator.fb.increment_fifo_read_pos 52 | TOP.top_testpic_generator.fb.convolver_input_valid 53 | TOP.top_testpic_generator.fb.fifo_readout_valid 54 | TOP.top_testpic_generator.fb.discard_words[2:0] 55 | @22 56 | TOP.top_testpic_generator.fb.rgb_conv_in.b[7:0] 57 | TOP.top_testpic_generator.fb.rgb_conv_in.g[7:0] 58 | TOP.top_testpic_generator.fb.rgb_conv_in.r[7:0] 59 | @28 60 | TOP.top_testpic_generator.fb.even_field 61 | @22 62 | TOP.top_testpic_generator.fb.fifo[0][63:0] 63 | TOP.top_testpic_generator.fb.fifo[1][63:0] 64 | TOP.top_testpic_generator.fb.fifo[2][63:0] 65 | TOP.top_testpic_generator.fb.fifo[3][63:0] 66 | TOP.top_testpic_generator.fb.fifo[4][63:0] 67 | TOP.top_testpic_generator.fb.fifo[5][63:0] 68 | TOP.top_testpic_generator.fb.fifo[6][63:0] 69 | TOP.top_testpic_generator.fb.fifo[7][63:0] 70 | TOP.top_testpic_generator.fb.fifo[8][63:0] 71 | TOP.top_testpic_generator.fb.fifo[9][63:0] 72 | TOP.top_testpic_generator.fb.fifo[10][63:0] 73 | TOP.top_testpic_generator.fb.fifo[11][63:0] 74 | TOP.top_testpic_generator.fb.fifo[12][63:0] 75 | TOP.top_testpic_generator.fb.fifo[13][63:0] 76 | TOP.top_testpic_generator.fb.fifo[14][63:0] 77 | TOP.top_testpic_generator.fb.fifo[15][63:0] 78 | TOP.top_testpic_generator.fb.fifo_free_entries_d[3:0] 79 | TOP.top_testpic_generator.fb.fifo_free_entries_q[3:0] 80 | TOP.top_testpic_generator.fb.fifo_read_pos[4:0] 81 | TOP.top_testpic_generator.fb.fifo_read_pos_q[4:0] 82 | TOP.top_testpic_generator.fb.fifo_read_word[63:0] 83 | TOP.top_testpic_generator.fb.fifo_write_pos[3:0] 84 | TOP.top_testpic_generator.fb.fifo_write_pos_q[3:0] 85 | TOP.top_testpic_generator.fb.height[8:0] 86 | TOP.top_testpic_generator.fb.line_addr[20:0] 87 | TOP.top_testpic_generator.fb.read_addr[20:0] 88 | TOP.top_testpic_generator.fb.start_addr_even_field[20:0] 89 | TOP.top_testpic_generator.fb.start_addr_odd_field[20:0] 90 | @28 91 | TOP.top_testpic_generator.fb.newframe 92 | TOP.top_testpic_generator.fb.newline 93 | @22 94 | TOP.top_testpic_generator.fb.pixel_count[5:0] 95 | TOP.top_testpic_generator.fb.pixel_data[31:0] 96 | TOP.top_testpic_generator.fb.pixel_x[9:0] 97 | @28 98 | TOP.top_testpic_generator.fb.restart_line 99 | TOP.top_testpic_generator.fb.rgb_mode 100 | @22 101 | TOP.top_testpic_generator.fb.stride[15:0] 102 | TOP.top_testpic_generator.fb.video_x[12:0] 103 | TOP.top_testpic_generator.fb.video_y[8:0] 104 | TOP.top_testpic_generator.fb.width[9:0] 105 | TOP.top_testpic_generator.fb.window_h_start[7:0] 106 | TOP.top_testpic_generator.fb.windows_v_start[6:0] 107 | TOP.top_testpic_generator.fb.windows_v_start_9bit_d[8:0] 108 | TOP.top_testpic_generator.fb.windows_v_start_9bit_q[8:0] 109 | [pattern_trace] 1 110 | [pattern_trace] 0 111 | -------------------------------------------------------------------------------- /sim/filter_int_5tap.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module filter_int_5tap ( 4 | input clk, 5 | input int in, 6 | output int out, 7 | 8 | input int b0, 9 | input int b1, 10 | input int b2, 11 | input int b3, 12 | input int b4, 13 | 14 | input int a1, 15 | input int a2, 16 | input int a3, 17 | input int a4, 18 | 19 | input int a_precision, 20 | input int b_precision 21 | ); 22 | 23 | localparam TAPS = 5; 24 | 25 | int rz[0:TAPS-1]; 26 | int lz[0:TAPS-1]; 27 | 28 | int x; 29 | 30 | function automatic int reduce(input int value, input int shift); 31 | begin 32 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 33 | end 34 | endfunction 35 | 36 | int v; 37 | int y; 38 | 39 | int rz_next[0:TAPS-1]; 40 | int lz_next[0:TAPS-1]; 41 | 42 | always_comb begin 43 | v = rz[0] + x; 44 | y = lz[0] + reduce(b0 * v, b_precision); 45 | 46 | rz_next[0] = reduce((-a1 * v), a_precision) + rz[1]; 47 | rz_next[1] = reduce((-a2 * v), a_precision) + rz[2]; 48 | rz_next[2] = reduce((-a3 * v), a_precision) + rz[3]; 49 | rz_next[3] = reduce((-a4 * v), a_precision); 50 | 51 | lz_next[0] = reduce((b1 * v), b_precision) + lz[1]; 52 | lz_next[1] = reduce((b2 * v), b_precision) + lz[2]; 53 | lz_next[2] = reduce((b3 * v), b_precision) + lz[3]; 54 | lz_next[3] = reduce((b4 * v), b_precision); 55 | end 56 | 57 | int i; 58 | 59 | initial begin 60 | for (i = 0; i < TAPS; i++) begin 61 | rz[i] = 0; 62 | lz[i] = 0; 63 | end 64 | end 65 | 66 | always_ff @(posedge clk) begin 67 | x <= in; // add 1 tick delay but keeps pathes short 68 | out <= y; // add 1 tick delay but keeps pathes short 69 | 70 | for (i = 0; i < TAPS; i++) begin 71 | // Right side of Transposed-Direct-Form-I which is the feedback path 72 | rz[i] <= rz_next[i]; // result is .0 after sum 73 | // Left side of Transposed-Direct-Form-I which does the forward path 74 | lz[i] <= lz_next[i]; // result is .A_AFTER_DOT to keep precision 75 | 76 | end 77 | end 78 | 79 | endmodule 80 | -------------------------------------------------------------------------------- /sim/filter_int_5tap_floorA.sv: -------------------------------------------------------------------------------- 1 | `include "coefficients.svh" 2 | 3 | module filter_int_5tap_floorA ( 4 | input clk, 5 | input int in, 6 | output int out, 7 | 8 | input int b0, 9 | input int b1, 10 | input int b2, 11 | input int b3, 12 | input int b4, 13 | 14 | input int a1, 15 | input int a2, 16 | input int a3, 17 | input int a4, 18 | 19 | input int a_precision, 20 | input int b_precision 21 | ); 22 | 23 | localparam TAPS = 5; 24 | 25 | int rz[0:TAPS-1]; 26 | int lz[0:TAPS-1]; 27 | 28 | int x; 29 | function automatic int reduce(input int value, input int shift); 30 | begin 31 | reduce = (value + (1 <<< (shift - 1))) >>> shift; 32 | end 33 | endfunction 34 | 35 | function automatic int reduce2(input int value, input int shift); 36 | begin 37 | reduce2 = value >>> shift; 38 | end 39 | endfunction 40 | 41 | 42 | int v; 43 | int y; 44 | 45 | int rz_next[0:TAPS-1]; 46 | int lz_next[0:TAPS-1]; 47 | 48 | always_comb begin 49 | v = rz[0] + x; 50 | y = lz[0] + reduce(b0 * v, b_precision); 51 | 52 | rz_next[0] = reduce2((-a1 * v), a_precision) + rz[1]; 53 | rz_next[1] = reduce2((-a2 * v), a_precision) + rz[2]; 54 | rz_next[2] = reduce2((-a3 * v), a_precision) + rz[3]; 55 | rz_next[3] = reduce2((-a4 * v), a_precision); 56 | 57 | lz_next[0] = reduce((b1 * v), b_precision) + lz[1]; 58 | lz_next[1] = reduce((b2 * v), b_precision) + lz[2]; 59 | lz_next[2] = reduce((b3 * v), b_precision) + lz[3]; 60 | lz_next[3] = reduce((b4 * v), b_precision); 61 | end 62 | 63 | int i; 64 | 65 | initial begin 66 | for (i = 0; i < TAPS; i++) begin 67 | rz[i] = 0; 68 | lz[i] = 0; 69 | end 70 | end 71 | 72 | always_ff @(posedge clk) begin 73 | x <= in; // add 1 tick delay but keeps pathes short 74 | out <= y; // add 1 tick delay but keeps pathes short 75 | 76 | for (i = 0; i < TAPS; i++) begin 77 | // Right side of Transposed-Direct-Form-I which is the feedback path 78 | rz[i] <= rz_next[i]; // result is .0 after sum 79 | // Left side of Transposed-Direct-Form-I which does the forward path 80 | lz[i] <= lz_next[i]; // result is .A_AFTER_DOT to keep precision 81 | 82 | end 83 | end 84 | 85 | endmodule 86 | -------------------------------------------------------------------------------- /sim/pal_verify_chromafilter.v: -------------------------------------------------------------------------------- 1 | // Implements IIR filter 2 | // Used https://ccrma.stanford.edu/~jos/fp/Transposed_Direct_Forms.html 3 | // for inspiration. The advantage of this approach is not having 4 | // two multiplications on one signal path between registers 5 | 6 | module pal_verify_chromafilter ( 7 | input clk, 8 | input signed [7:0] in, 9 | output reg signed [7:0] out 10 | ); 11 | 12 | parameter BIT_WIDTH = 31; 13 | 14 | localparam b_after_dot = `PAL_CHROMA_B_AFTER_DOT; 15 | localparam a_after_dot = `PAL_CHROMA_A_AFTER_DOT; 16 | localparam b_to_a_diff_after_dot = `PAL_CHROMA_B_TO_A_DIFF_AFTER_DOT; 17 | 18 | localparam signed [BIT_WIDTH:0] b0 = `PAL_CHROMA_B0; //.CHROMA_B_AFTER_DOT 19 | localparam signed [BIT_WIDTH:0] b1 = `PAL_CHROMA_B1; //.CHROMA_B_AFTER_DOT 20 | localparam signed [BIT_WIDTH:0] b2 = `PAL_CHROMA_B2; //.CHROMA_B_AFTER_DOT 21 | localparam signed [BIT_WIDTH:0] b3 = `PAL_CHROMA_B3; //.CHROMA_B_AFTER_DOT 22 | localparam signed [BIT_WIDTH:0] b4 = `PAL_CHROMA_B4; //.CHROMA_B_AFTER_DOT 23 | 24 | localparam signed [BIT_WIDTH:0] a1 = `PAL_CHROMA_A1; //.CHROMA_A_AFTER_DOT 25 | localparam signed [BIT_WIDTH:0] a2 = `PAL_CHROMA_A2; //.CHROMA_A_AFTER_DOT 26 | localparam signed [BIT_WIDTH:0] a3 = `PAL_CHROMA_A3; //.CHROMA_A_AFTER_DOT 27 | localparam signed [BIT_WIDTH:0] a4 = `PAL_CHROMA_A4; //.CHROMA_A_AFTER_DOT 28 | 29 | reg signed [BIT_WIDTH:0] rz[0:3]; //.0 30 | reg signed [BIT_WIDTH:0] lz[0:3]; //.0 31 | 32 | reg signed [7:0] x; //.0 33 | 34 | wire signed [BIT_WIDTH:0] v = rz[0] + BIT_WIDTH'(x); //.0 35 | wire signed [7:0] y = 8'((lz[0] + ((b0 * v) >>> b_to_a_diff_after_dot)) >> a_after_dot); //.0 36 | 37 | always @(posedge clk) begin 38 | x <= in; // add 1 tick delay but keeps pathes short 39 | out <= y; // add 1 tick delay but keeps pathes short 40 | 41 | // Right side of Transposed-Direct-Form-I which is the feedback path 42 | rz[0] <= ((-a1 * v) >>> a_after_dot) + rz[1]; // result is .0 after sum 43 | rz[1] <= ((-a2 * v) >>> a_after_dot) + rz[2]; // result is .0 44 | rz[2] <= ((-a3 * v) >>> a_after_dot) + rz[3]; // result is .0 45 | rz[3] <= ((-a4 * v) >>> a_after_dot); // result is .0 46 | 47 | // Left side of Transposed-Direct-Form-I which does the forward path 48 | lz[0] <= ((b1 * v) >>> b_to_a_diff_after_dot) + lz[1]; // result is .CHROMA_A_AFTER_DOT to keep precision 49 | lz[1] <= ((b2 * v) >>> b_to_a_diff_after_dot) + lz[2]; // result is .CHROMA_A_AFTER_DOT to keep precision 50 | lz[2] <= ((b3 * v) >>> b_to_a_diff_after_dot) + lz[3]; // result is .CHROMA_A_AFTER_DOT to keep precision 51 | lz[3] <= ((b4 * v) >>> b_to_a_diff_after_dot); // result is .CHROMA_A_AFTER_DOT to keep precision 52 | end 53 | 54 | endmodule 55 | -------------------------------------------------------------------------------- /sim/pal_verify_lumafilter.sv: -------------------------------------------------------------------------------- 1 | module pal_verify_lumafilter ( 2 | input clk, 3 | input int in, 4 | output int out 5 | ); 6 | 7 | localparam int b0 = `PAL_LUMA_LOWPASS_B0; 8 | localparam int b1 = `PAL_LUMA_LOWPASS_B1; 9 | localparam int b2 = `PAL_LUMA_LOWPASS_B2; 10 | 11 | localparam int a1 = -(`PAL_LUMA_LOWPASS_A1); 12 | localparam int a2 = -(`PAL_LUMA_LOWPASS_A2); 13 | 14 | localparam int a_precision = `PAL_LUMA_LOWPASS_A_AFTER_DOT; 15 | localparam int b_precision = `PAL_LUMA_LOWPASS_B_AFTER_DOT; 16 | 17 | int rz0_d; 18 | int rz1_d; 19 | int lz0_d; 20 | int lz1_d; 21 | 22 | int rz0_q = 0; 23 | int rz1_q = 0; 24 | int lz0_q = 0; 25 | int lz0_q2 = 0; 26 | int lz1_q = 0; 27 | 28 | int x; 29 | 30 | function automatic int reduce(input int value, input int shift); 31 | begin 32 | reduce = value >>> shift; 33 | end 34 | endfunction 35 | 36 | int v; 37 | int v_q; 38 | int y; 39 | 40 | int v0_mul_b0_d; 41 | int v0_mul_b0_q; 42 | 43 | always_comb begin 44 | v = rz0_q + x; 45 | rz0_d = reduce((a1 * v), a_precision) + rz1_q; 46 | rz1_d = reduce((a2 * v), a_precision); 47 | 48 | v0_mul_b0_d = reduce(b0 * v_q, b_precision); 49 | 50 | y = v0_mul_b0_q + lz0_q2; 51 | lz0_d = reduce((b1 * v_q), b_precision) + lz1_q; 52 | lz1_d = reduce((b2 * v_q), b_precision); 53 | end 54 | 55 | always_ff @(posedge clk) begin 56 | x <= in <<< 2; // add 1 tick delay but keeps pathes short 57 | 58 | // just to be sure that the output is always positive 59 | if (y < 0) out <= 0; 60 | else out <= (y + 2) >>> 2; // add 1 tick delay but keeps pathes short 61 | 62 | rz0_q <= rz0_d; 63 | rz1_q <= rz1_d; 64 | lz0_q <= lz0_d; 65 | lz1_q <= lz1_d; 66 | 67 | lz0_q2 <= lz0_q; 68 | v_q <= v; 69 | 70 | v0_mul_b0_q <= v0_mul_b0_d; 71 | end 72 | 73 | endmodule 74 | -------------------------------------------------------------------------------- /sim/psram_emu.sv: -------------------------------------------------------------------------------- 1 | 2 | module PSRAM_Memory_Interface_HS_V2_Top ( 3 | clk_d, 4 | memory_clk, 5 | memory_clk_p, 6 | pll_lock, 7 | rst_n, 8 | O_psram_ck, 9 | O_psram_ck_n, 10 | IO_psram_dq, 11 | IO_psram_rwds, 12 | O_psram_cs_n, 13 | O_psram_reset_n, 14 | wr_data, 15 | rd_data, 16 | rd_data_valid, 17 | addr, 18 | cmd, 19 | cmd_en, 20 | init_calib, 21 | clk_out, 22 | data_mask 23 | ); 24 | input clk_d; 25 | input memory_clk; 26 | input memory_clk_p; 27 | input pll_lock; 28 | input rst_n; 29 | output [1:0] O_psram_ck; 30 | output [1:0] O_psram_ck_n; 31 | inout [15:0] IO_psram_dq; 32 | inout [1:0] IO_psram_rwds; 33 | output [1:0] O_psram_cs_n; 34 | output [1:0] O_psram_reset_n; 35 | input [63:0] wr_data; 36 | output bit [63:0] rd_data; 37 | output bit rd_data_valid=0; 38 | input [20:0] addr; 39 | input cmd; 40 | input cmd_en; 41 | output bit init_calib = 1; 42 | input clk_out; 43 | input [7:0] data_mask; 44 | 45 | 46 | bit [20:0] current_addr; 47 | int cycle = 0; 48 | bit active = 0; 49 | bit cmd_latch = 0; 50 | bit [7:0] memory [0:1024*1024]; 51 | 52 | int returnvalue=0; 53 | 54 | always_ff @(posedge clk_out) begin 55 | if (cmd_en && !active) begin 56 | 57 | if (cmd) 58 | current_addr <= addr+1; 59 | else 60 | current_addr <= addr; 61 | 62 | cycle <= 0; 63 | active<=1; 64 | cmd_latch <= cmd; 65 | if (cmd) begin 66 | $display("Write %0h %0h mask %0h",addr,wr_data,data_mask); 67 | memory[addr] <= wr_data[7:0]; 68 | end 69 | else begin 70 | //$display("Read %0h",addr); 71 | end 72 | end 73 | 74 | if (active) 75 | cycle <= cycle + 1; 76 | 77 | if (cycle==13)begin 78 | active <= 0; 79 | //$display("Complete"); 80 | end 81 | rd_data[63:56] <= 8'(returnvalue+0); 82 | rd_data[55:48] <= 8'(returnvalue+1); 83 | rd_data[47:40] <= 8'(returnvalue+2); 84 | rd_data[39:32] <= 8'(returnvalue+3); 85 | rd_data[31:24] <= 8'(returnvalue+4); 86 | rd_data[23:16] <= 8'(returnvalue+5); 87 | rd_data[15:8] <= 8'(returnvalue+6); 88 | rd_data[7:0] <= 8'(returnvalue+7); 89 | 90 | if (cycle>=8 && cycle<=11) 91 | returnvalue <= returnvalue+8; 92 | 93 | if (cycle==8+4)rd_data_valid <= 0; 94 | if (cycle==8) begin 95 | if (!cmd_latch) begin 96 | rd_data_valid<=1; 97 | //$display("Read %0h %0h",current_addr,memory[current_addr]); 98 | 99 | end 100 | end 101 | 102 | end 103 | endmodule 104 | -------------------------------------------------------------------------------- /sim/secam.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.116 (w)1999-2023 BSI 3 | [*] Fri Jan 5 18:31:30 2024 4 | [*] 5 | [dumpfile] "/home/andre/GIT/fpga_pong/sim/waveform.vcd" 6 | [dumpfile_mtime] "Fri Jan 5 18:22:08 2024" 7 | [dumpfile_size] 832329183 8 | [savefile] "/home/andre/GIT/fpga_pong/sim/secam.gtkw" 9 | [timestart] 616060 10 | [size] 2560 1348 11 | [pos] -1 -1 12 | *-12.650105 648247 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] TOP. 14 | [treeopen] TOP.top_testpic_generator. 15 | [treeopen] TOP.top_testpic_generator.cvbs. 16 | [sst_width] 310 17 | [signals_width] 441 18 | [sst_expanded] 1 19 | [sst_vpaned_height] 415 20 | @22 21 | TOP.top_testpic_generator.cvbs.secam.enabled_amplitude[5:0] 22 | @8022 23 | TOP.top_testpic_generator.cvbs.secam.enabled_amplitude[5:0] 24 | @20000 25 | - 26 | - 27 | @8022 28 | TOP.top_testpic_generator.cvbs.secam.enabled_amplitude_filtered[5:0] 29 | @20000 30 | - 31 | - 32 | @22 33 | TOP.top_testpic_generator.cvbs.secam.enabled_amplitude_filtered[5:0] 34 | @8420 35 | TOP.top_testpic_generator.cvbs.secam.carrier_period_modulate[8:0] 36 | @20000 37 | - 38 | - 39 | @8420 40 | TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis[8:0] 41 | @20000 42 | - 43 | - 44 | @808421 45 | TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 46 | @29 47 | (0)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 48 | (1)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 49 | (2)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 50 | (3)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 51 | (4)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 52 | (5)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 53 | (6)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 54 | (7)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 55 | (8)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 56 | (9)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 57 | (10)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 58 | (11)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 59 | (12)TOP.top_testpic_generator.cvbs.secam.carrier_period_emphasis2[12:0] 60 | @1001201 61 | -group_end 62 | @20000 63 | - 64 | - 65 | @8024 66 | TOP.top_testpic_generator.cvbs.secam.phase_increment[50:0] 67 | @20000 68 | - 69 | - 70 | @8420 71 | TOP.top_testpic_generator.cvbs.secam.chroma[7:0] 72 | @20000 73 | - 74 | @800028 75 | TOP.top_testpic_generator.testpattern.rgb[2:0] 76 | @28 77 | (0)TOP.top_testpic_generator.testpattern.rgb[2:0] 78 | (1)TOP.top_testpic_generator.testpattern.rgb[2:0] 79 | (2)TOP.top_testpic_generator.testpattern.rgb[2:0] 80 | @1001200 81 | -group_end 82 | [pattern_trace] 1 83 | [pattern_trace] 0 84 | -------------------------------------------------------------------------------- /sim/sim_pixel_convolver.cpp: -------------------------------------------------------------------------------- 1 | // Include common routines 2 | #include 3 | #include 4 | 5 | // Include model header, generated from Verilating "top.v" 6 | #include "Vpixel_convolver.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void clockcycle(Vpixel_convolver &dut, VerilatedVcdC &m_trace) 15 | { 16 | static vluint64_t sim_time = 0; 17 | 18 | // evaluate internal combinatoric according to possible external input 19 | if (dut.clk == 1) 20 | { 21 | dut.eval(); 22 | m_trace.dump(sim_time); 23 | sim_time++; 24 | } 25 | 26 | // clock down and eval 27 | dut.clk = 0; 28 | dut.eval(); 29 | m_trace.dump(sim_time); 30 | sim_time++; 31 | 32 | // clock up and eval 33 | dut.clk = 1; 34 | dut.eval(); 35 | } 36 | 37 | int main(int argc, char **argv) 38 | { 39 | Verilated::commandArgs(argc, argv); 40 | Vpixel_convolver dut; 41 | VerilatedVcdC m_trace; 42 | Verilated::traceEverOn(true); 43 | 44 | dut.trace(&m_trace, 5); 45 | m_trace.open("waveform.vcd"); 46 | 47 | std::list input; 48 | 49 | input.push_back(0x01020304); 50 | input.push_back(0x05060708); 51 | input.push_back(0x090a0b0c); 52 | input.push_back(0x0d0e0f10); 53 | input.push_back(0x11121314); 54 | 55 | std::list groundtruth; 56 | groundtruth.push_back(0x010203); 57 | groundtruth.push_back(0x040506); 58 | groundtruth.push_back(0x070809); 59 | groundtruth.push_back(0x0a0b0c); 60 | groundtruth.push_back(0x0d0e0f); 61 | groundtruth.push_back(0x101213); 62 | 63 | std::list output; 64 | 65 | clockcycle(dut, m_trace); 66 | dut.reset = 1; 67 | clockcycle(dut, m_trace); 68 | dut.input_valid = 1; 69 | dut.reset = 0; 70 | dut.in32 = input.front(); 71 | input.pop_front(); 72 | clockcycle(dut, m_trace); 73 | 74 | for (int i = 0; i < 15; i++) 75 | { 76 | dut.strobe_out24 = dut.out24_ready; 77 | dut.eval(); 78 | if (dut.strobe_input) 79 | { 80 | if (input.empty()) 81 | { 82 | dut.input_valid = 0; 83 | } 84 | else 85 | { 86 | dut.input_valid = 1; 87 | dut.in32 = input.front(); 88 | input.pop_front(); 89 | } 90 | } 91 | if (dut.out24_ready) 92 | { 93 | // printf("Got data %06x\n", dut.out24); 94 | output.push_back(dut.out24); 95 | } 96 | 97 | clockcycle(dut, m_trace); 98 | } 99 | 100 | while (!groundtruth.empty() && !output.empty()) 101 | { 102 | printf("%06x ==? %06x\n", groundtruth.front(), output.front()); 103 | groundtruth.pop_front(); 104 | output.pop_front(); 105 | } 106 | 107 | // assert(groundtruth.empty()); 108 | // assert(output.empty()); 109 | printf("Success\n"); 110 | 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /sim/sim_pixel_convolver.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | verilator --top-module pixel_convolver \ 4 | --trace --cc --assert --exe --build sim_pixel_convolver.cpp \ 5 | ../rtl/*.svh ../rtl/*.sv ../rtl/filter/*.sv ../rtl/*.v \ 6 | -I../rtl 7 | 8 | ./obj_dir/Vpixel_convolver 9 | 10 | # gtkwave waveform.vcd 11 | -------------------------------------------------------------------------------- /sim/sim_rgb2ycbcr.cpp: -------------------------------------------------------------------------------- 1 | // Include common routines 2 | #include 3 | #include 4 | 5 | // Include model header, generated from Verilating "top.v" 6 | #include "VRGB2YCbCr.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char **argv) 13 | { 14 | Verilated::commandArgs(argc, argv); 15 | VRGB2YCbCr dut; 16 | 17 | printf("Checking edge cases of RGB to YCbCr conversion...\n"); 18 | 19 | for (int i = 0; i < 16; i++) 20 | { 21 | uint8_t maxval = i & 8 ? 255 : 191; 22 | 23 | dut.R = i & 1 ? maxval : 0; 24 | dut.G = i & 2 ? maxval : 0; 25 | dut.B = i & 4 ? maxval : 0; 26 | 27 | dut.clk = 1; 28 | dut.eval(); 29 | dut.clk = 0; 30 | dut.eval(); 31 | 32 | dut.clk = 1; 33 | dut.eval(); 34 | dut.clk = 0; 35 | dut.eval(); 36 | 37 | uint8_t Y = dut.Y; 38 | int8_t Cb = dut.Cb; 39 | int8_t Cr = dut.Cr; 40 | 41 | // Translation matrix from https://en.wikipedia.org/wiki/YCbCr 42 | // Approximate 8-bit matrices for BT.601 as full swing 43 | float verify_Y = (77 * dut.R + 150 * dut.G + 29 * dut.B) / 256.0; 44 | float verify_Cb = (-43 * dut.R - 84 * dut.G + 127 * dut.B) / 256.0; 45 | float verify_Cr = (127 * dut.R - 106 * dut.G - 21 * dut.B) / 256.0; 46 | 47 | printf("%3d %3d %3d -> %3d %3d %3d == %3.1f %3.1f %3.1f ? \n", dut.R, dut.G, dut.B, Y, Cb, Cr, verify_Y, verify_Cb, verify_Cr); 48 | 49 | assert(fabs(Y - verify_Y) < 2.1); 50 | assert(fabs(Cb - verify_Cb) < 2.1); 51 | assert(fabs(Cr - verify_Cr) < 2.1); 52 | } 53 | 54 | printf("Success\n"); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /sim/sim_rgb2ycbcr.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | verilator --top-module RGB2YCbCr \ 4 | --trace --cc --assert --exe --build sim_rgb2ycbcr.cpp \ 5 | ../rtl/*.svh ../rtl/*.sv ../rtl/filter/*.sv ../rtl/*.v \ 6 | -I../rtl 7 | 8 | ./obj_dir/VRGB2YCbCr 9 | 10 | # gtkwave waveform.vcd 11 | -------------------------------------------------------------------------------- /sim/sim_sinus.cpp: -------------------------------------------------------------------------------- 1 | // Include common routines 2 | #include 3 | #include 4 | 5 | // Include model header, generated from Verilating "top.v" 6 | #include "Vsinus.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char **argv) 13 | { 14 | Verilated::commandArgs(argc, argv); 15 | Vsinus dut; 16 | 17 | printf("Checking whole range of sine values and compare against reference...\n"); 18 | for (int amplitude = -32; amplitude < 31; amplitude++) 19 | { 20 | printf("%3d ", amplitude); 21 | for (int phase = 0; phase < 32; phase++) 22 | { 23 | dut.phase = phase; 24 | dut.amplitude = amplitude; 25 | dut.clk = 1; 26 | dut.eval(); 27 | dut.clk = 0; 28 | dut.eval(); 29 | 30 | dut.clk = 1; 31 | dut.eval(); 32 | dut.clk = 0; 33 | dut.eval(); 34 | 35 | int8_t result = dut.out; 36 | 37 | int8_t verify_val = round(amplitude * 2.0 * sin(phase * M_PI * 2 / 32)); 38 | assert(result == verify_val); 39 | printf(" %3d", verify_val); 40 | } 41 | printf("\n"); 42 | } 43 | 44 | printf("Success\n"); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /sim/sim_sinus.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | verilator --top-module sinus \ 4 | --trace --cc --assert --exe --build sim_sinus.cpp \ 5 | ../rtl/*.svh ../rtl/*.sv ../rtl/filter/*.sv ../rtl/*.v \ 6 | -I../rtl 7 | 8 | ./obj_dir/Vsinus 9 | 10 | # gtkwave waveform.vcd 11 | -------------------------------------------------------------------------------- /sim/sim_top.cpp: -------------------------------------------------------------------------------- 1 | // Include common routines 2 | #include 3 | #include 4 | 5 | // Include model header, generated from Verilating "top.v" 6 | #include "Vtop_testpic_generator.h" 7 | #include "Vtop_testpic_generator___024root.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | const int width = 0x0c00U; 14 | const int lines = 800; 15 | const int stretch = 1; 16 | const int height = lines * stretch; 17 | vluint64_t sim_time = 0; 18 | 19 | uint8_t output_image[width * height] = {0}; 20 | 21 | void write_png_file(const char *filename) 22 | { 23 | int y; 24 | 25 | FILE *fp = fopen(filename, "wb"); 26 | if (!fp) 27 | abort(); 28 | 29 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 30 | if (!png) 31 | abort(); 32 | 33 | png_infop info = png_create_info_struct(png); 34 | if (!info) 35 | abort(); 36 | 37 | if (setjmp(png_jmpbuf(png))) 38 | abort(); 39 | 40 | png_init_io(png, fp); 41 | 42 | // Output is 8bit depth, RGBA format. 43 | png_set_IHDR( 44 | png, 45 | info, 46 | width, height, 47 | 8, 48 | PNG_COLOR_TYPE_GRAY, 49 | PNG_INTERLACE_NONE, 50 | PNG_COMPRESSION_TYPE_DEFAULT, 51 | PNG_FILTER_TYPE_DEFAULT); 52 | png_write_info(png, info); 53 | 54 | png_bytepp row_pointers = (png_bytepp)png_malloc(png, sizeof(png_bytepp) * height); 55 | 56 | for (int i = 0; i < height; i++) 57 | { 58 | row_pointers[i] = &output_image[width * i]; 59 | } 60 | 61 | png_write_image(png, row_pointers); 62 | png_write_end(png, NULL); 63 | 64 | free(row_pointers); 65 | 66 | fclose(fp); 67 | 68 | png_destroy_write_struct(&png, &info); 69 | } 70 | 71 | int main(int argc, char **argv) 72 | { 73 | // Initialize Verilators variables 74 | Verilated::commandArgs(argc, argv); 75 | Verilated::traceEverOn(true); 76 | 77 | VerilatedVcdC m_trace; 78 | Vtop_testpic_generator dut; 79 | 80 | dut.trace(&m_trace, 5); 81 | m_trace.open("waveform.vcd"); 82 | 83 | dut.switch1 = 1; 84 | 85 | dut.rootp->top_testpic_generator__DOT__clk = 0; 86 | dut.eval(); 87 | dut.rootp->top_testpic_generator__DOT__clk = 1; 88 | dut.eval(); 89 | 90 | for (int y = 0; y < lines; y++) 91 | { 92 | for (int x = 0; x < width; x++) 93 | { 94 | dut.rootp->top_testpic_generator__DOT__clk = 0; 95 | dut.eval(); 96 | m_trace.dump(sim_time); 97 | sim_time++; 98 | 99 | dut.rootp->top_testpic_generator__DOT__clk = 1; 100 | dut.eval(); 101 | m_trace.dump(sim_time); 102 | sim_time++; 103 | 104 | for (int j = 0; j < stretch; j++) 105 | { 106 | assert((y * width * stretch + width * j + x) < (width * height)); 107 | 108 | // output_image[y * width * stretch + width * j + x] = 127 + dut.rootp->top_testpic_generator__DOT__chroma; 109 | output_image[y * width * stretch + width * j + x] = dut.video; 110 | } 111 | } 112 | } 113 | 114 | write_png_file("raw_video.png"); 115 | 116 | return 0; 117 | } 118 | -------------------------------------------------------------------------------- /sim/sim_top.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | verilator --top-module top_testpic_generator \ 4 | --trace --cc --assert --exe --build sim_top.cpp \ 5 | ../rtl/*.svh ../rtl/*.sv ../rtl/filter/*.sv ../rtl/*.v \ 6 | -I../rtl psram_emu.sv \ 7 | /usr/lib/x86_64-linux-gnu/libpng.so 8 | 9 | ./obj_dir/Vtop_testpic_generator 10 | eog raw_video.png 11 | 12 | # gtkwave waveform.vcd 13 | -------------------------------------------------------------------------------- /sim2/tb_top.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ps 2 | 3 | module tb_top; 4 | 5 | logic clk27 = 0; 6 | logic switch1 = 0; 7 | logic sys_resetn = 0; 8 | logic [7:0] video; 9 | logic [7:6] video_extra; 10 | logic uart_rx = 1; 11 | logic uart_tx; 12 | 13 | wire O_psram_ck; 14 | wire O_psram_ck_n; 15 | wire IO_psram_rwds; 16 | wire [7:0] IO_psram_dq; 17 | wire O_psram_reset_n; 18 | wire O_psram_cs_n; 19 | 20 | wire sec_O_psram_ck; 21 | wire sec_O_psram_ck_n; 22 | wire sec_IO_psram_rwds; 23 | wire [7:0] sec_IO_psram_dq; 24 | wire sec_O_psram_reset_n; 25 | wire sec_O_psram_cs_n; 26 | wire [5:0] led; 27 | 28 | top_testpic_generator dut( 29 | .clk27, 30 | .switch1, 31 | .led, 32 | .sys_resetn, 33 | .video, 34 | .video_extra, 35 | .uart_rx, 36 | .uart_tx, 37 | .O_psram_ck({sec_O_psram_ck, O_psram_ck}), 38 | .O_psram_ck_n({sec_O_psram_ck_n, O_psram_ck_n}), 39 | .IO_psram_rwds({sec_IO_psram_rwds, IO_psram_rwds}), 40 | .IO_psram_dq({sec_IO_psram_dq, IO_psram_dq}), 41 | .O_psram_reset_n({sec_O_psram_reset_n, O_psram_reset_n}), 42 | .O_psram_cs_n({sec_O_psram_cs_n, O_psram_cs_n}) 43 | ); 44 | 45 | //always #10 dut.clk = !dut.clk; 46 | //always #10 clk27 = !clk27; 47 | 48 | initial begin 49 | force dut.clk = 0; 50 | #5 forever #10 force dut.clk = !dut.clk; 51 | end 52 | 53 | //assign IO_psram_dq = {weak0,weak0,weak0,weak0,weak1,weak0,weak0,weak0}; 54 | assign (pull1, pull0) IO_psram_dq = 8'b11000011; 55 | 56 | initial begin 57 | 58 | //dut.clk = 0; 59 | //dut.clk_p = 0; 60 | //force dut.fb.fifo_read_word = 64'h8040201080402010; 61 | //force dut.ebu75_active = 1; 62 | 63 | //#100 sys_resetn = 1; 64 | //force dut.calib = 1; 65 | 66 | /* 67 | for ( int i = 0; i < 32 ; i++) begin 68 | @(posedge dut.clk) force dut.bw_data = 1; force dut.bw_strobe = 1; 69 | @(posedge dut.clk) force dut.bw_data = 1; force dut.bw_strobe = 0; 70 | end 71 | */ 72 | /* 73 | @(negedge dut.busy) @(posedge dut.clk) dut.i_com_data = "W"; 74 | dut.i_com_strobe = 1; 75 | @(posedge dut.clk) dut.i_com_data = 00; 76 | dut.i_com_strobe = 1; 77 | @(posedge dut.clk) dut.i_com_data = 04; 78 | dut.i_com_strobe = 1; 79 | @(posedge dut.clk) dut.i_com_data = 42; 80 | dut.i_com_strobe = 1; 81 | @(posedge dut.clk) dut.i_com_strobe = 0; 82 | 83 | #300 @(posedge dut.clk) dut.i_com_data = "R"; 84 | dut.i_com_strobe = 1; 85 | @(posedge dut.clk) dut.i_com_data = 00; 86 | dut.i_com_strobe = 1; 87 | @(posedge dut.clk) dut.i_com_data = 04; 88 | dut.i_com_strobe = 1; 89 | @(posedge dut.clk) dut.i_com_strobe = 0; 90 | 91 | 92 | #2000 @(posedge dut.clk) dut.i_com_data = "R"; 93 | dut.i_com_strobe = 1; 94 | @(posedge dut.clk) dut.i_com_data = 00; 95 | dut.i_com_strobe = 1; 96 | @(posedge dut.clk) dut.i_com_data = 04; 97 | dut.i_com_strobe = 1; 98 | @(posedge dut.clk) dut.i_com_strobe = 0; 99 | */ 100 | 101 | end 102 | endmodule 103 | -------------------------------------------------------------------------------- /sim2/wave.do: -------------------------------------------------------------------------------- 1 | onerror {resume} 2 | quietly WaveActivateNextPane {} 0 3 | add wave -noupdate /tb_top/dut/fb/RegisterHighAddr 4 | add wave -noupdate /tb_top/dut/fb/newframe 5 | add wave -noupdate /tb_top/dut/fb/newline 6 | add wave -noupdate /tb_top/dut/fb/even_field 7 | add wave -noupdate /tb_top/dut/fb/video_y 8 | add wave -noupdate /tb_top/dut/fb/video_x 9 | add wave -noupdate /tb_top/dut/fb/out 10 | add wave -noupdate /tb_top/dut/fb/clk 11 | add wave -noupdate /tb_top/dut/fb/width 12 | add wave -noupdate /tb_top/dut/fb/stride 13 | add wave -noupdate /tb_top/dut/fb/height 14 | add wave -noupdate /tb_top/dut/fb/clk_per_pixel 15 | add wave -noupdate /tb_top/dut/fb/window_h_start 16 | add wave -noupdate /tb_top/dut/fb/start_addr_even_field 17 | add wave -noupdate /tb_top/dut/fb/start_addr_odd_field 18 | add wave -noupdate /tb_top/dut/fb/windows_v_start 19 | add wave -noupdate /tb_top/dut/fb/debug_line_mode 20 | add wave -noupdate /tb_top/dut/fb/rgb_mode 21 | add wave -noupdate /tb_top/dut/fb/windows_v_start_9bit_d 22 | add wave -noupdate /tb_top/dut/fb/windows_v_start_9bit_q 23 | add wave -noupdate /tb_top/dut/fb/visible_window 24 | add wave -noupdate /tb_top/dut/fb/pixel_count 25 | add wave -noupdate /tb_top/dut/fb/pixel_x 26 | add wave -noupdate /tb_top/dut/fb/discard_words 27 | add wave -noupdate /tb_top/dut/fb/read_addr 28 | add wave -noupdate /tb_top/dut/fb/line_addr 29 | add wave -noupdate /tb_top/dut/fb/fifo 30 | add wave -noupdate /tb_top/dut/fb/fifo_read_word 31 | add wave -noupdate /tb_top/dut/fb/fifo_read_pos 32 | add wave -noupdate /tb_top/dut/fb/fifo_read_pos_q 33 | add wave -noupdate /tb_top/dut/fb/fifo_write_pos 34 | add wave -noupdate /tb_top/dut/fb/fifo_free_entries_d 35 | add wave -noupdate /tb_top/dut/fb/fifo_free_entries_q 36 | add wave -noupdate /tb_top/dut/fb/pixel_data 37 | add wave -noupdate -radix hexadecimal -expand /tb_top/dut/fb/rgb_conv_out 38 | add wave -noupdate -radix hexadecimal -expand /tb_top/dut/fb/rgb_conv_in 39 | add wave -noupdate /tb_top/dut/fb/restart_line 40 | add wave -noupdate /tb_top/dut/fb/convolver_in32 41 | add wave -noupdate /tb_top/dut/fb/convolver_strobe_input 42 | add wave -noupdate /tb_top/dut/fb/convolver_input_valid 43 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/convolver_out24 44 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/convolver_out24_visible_window 45 | add wave -noupdate /tb_top/dut/fb/convolver_out24_ready 46 | add wave -noupdate /tb_top/dut/fb/convolver_strobe_out24 47 | add wave -noupdate /tb_top/dut/fb/convolver_pass_through 48 | add wave -noupdate /tb_top/dut/fb/bus/clk 49 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/bus/wr_data 50 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/bus/rd_data 51 | add wave -noupdate /tb_top/dut/fb/bus/cmd 52 | add wave -noupdate /tb_top/dut/fb/bus/cmd_en 53 | add wave -noupdate /tb_top/dut/fb/bus/addr 54 | add wave -noupdate /tb_top/dut/fb/bus/ready 55 | add wave -noupdate /tb_top/dut/fb/bus/data_mask 56 | add wave -noupdate /tb_top/dut/fb/bus/rd_data_valid 57 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/clk 58 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/reset 59 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/mode32 60 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/pixel_convolver/in32 61 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/strobe_input 62 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/input_valid 63 | add wave -noupdate -radix hexadecimal /tb_top/dut/fb/pixel_convolver/out24 64 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/out24_ready 65 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/strobe_out24 66 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/temp_mem 67 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/state 68 | add wave -noupdate /tb_top/dut/fb/pixel_convolver/update_output 69 | TreeUpdate [SetDefaultTree] 70 | WaveRestoreCursors {{Cursor 1} {1967038410 ps} 0} 71 | quietly wave cursor active 1 72 | configure wave -namecolwidth 253 73 | configure wave -valuecolwidth 163 74 | configure wave -justifyvalue left 75 | configure wave -signalnamewidth 0 76 | configure wave -snapdistance 10 77 | configure wave -datasetprefix 0 78 | configure wave -rowmargin 4 79 | configure wave -childrowmargin 2 80 | configure wave -gridoffset 0 81 | configure wave -gridperiod 1 82 | configure wave -griddelta 40 83 | configure wave -timeline 0 84 | configure wave -timelineunits ps 85 | update 86 | WaveRestoreZoom {1965666250 ps} {1967829502 ps} 87 | -------------------------------------------------------------------------------- /tools/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /tools/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /tools/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /tools/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tools/.idea/tools.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tools/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tools/color.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | 4 | # https://de.wikipedia.org/wiki/YUV-Farbmodell 5 | def ypbpr2yuv(y, pb, pr): 6 | return y, pb * 0.872021, pr * 1.229951 7 | 8 | 9 | def yuv2ypbpr(y, u, v): 10 | return y, u / 0.872021, v / 1.229951 11 | 12 | 13 | def yuv_real2raw(y, u, v): 14 | y_raw = round(numpy.interp(y, [0, 1], [0, 255])) 15 | u_raw = round(numpy.interp(u, [-1, 1], [-126, 126])) 16 | v_raw = round(numpy.interp(v, [-1, 1], [-126, 126])) 17 | return y_raw, u_raw, v_raw 18 | 19 | 20 | # https://de.wikipedia.org/wiki/Component_Video 21 | def rgb2yprpb(r, g, b): 22 | y = 0.299 * r + 0.587 * g + 0.114 * b 23 | pb = -0.168 * r - 0.331 * g + 0.5 * b 24 | pr = 0.5 * r - 0.418 * g - 0.081 * b 25 | return y, pb, pr 26 | 27 | 28 | # https://en.wikipedia.org/wiki/YCbCr 29 | def rgb2ycbcr(r, g, b): 30 | # This might surprise but the formulas are exactly the same 31 | # Only difference is the 128 offset for Cb and Cr 32 | # But we are ignoring these here as we are using signed bytes 33 | # Also the input being 0..255 instead of 0..1 34 | y, pb, pr = rgb2yprpb(r, g, b) 35 | return round(y), round(pb), round(pr) 36 | 37 | 38 | # https://www.pcmag.com/encyclopedia/term/yuvrgb-conversion-formulas 39 | def rgb2yuv(r, g, b): 40 | y = 0.299 * r + 0.587 * g + 0.114 * b 41 | u = 0.493 * (b - y) 42 | v = 0.877 * (r - y) 43 | return y, u, v 44 | 45 | 46 | def yuv2rgb(y, u, v): 47 | r = y + 1.140 * v 48 | g = y - 0.395 * u - 0.581 * v 49 | b = y + 2.032 * u 50 | return r, g, b 51 | -------------------------------------------------------------------------------- /tools/configure.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from scipy import signal 4 | 5 | from filterutil import FpFilter 6 | 7 | official_ntsc_frequency = 3.579545e6 8 | official_pal_frequency = 4.43361875e6 9 | secam_dr_frequency = 4.40626e6 10 | secam_db_frequency = 4.25e6 11 | secam_dr_hub = 280e3 12 | secam_db_hub = 230e3 13 | 14 | system_clock = 48e6 15 | system_clock_period = 1 / system_clock 16 | 17 | line_length = 64e-6 / system_clock_period # 64 usec 18 | frame_time = line_length * 262 * system_clock_period 19 | 20 | pal_burst_amplitude = 11 21 | pal_burst_u = round(pal_burst_amplitude * math.sin(math.radians(-45))) 22 | pal_burst_v = round(pal_burst_amplitude * math.cos(math.radians(-45))) 23 | ntsc_burst_amplitude = 15 24 | ntsc_burst_phase = -45 + 33 - 5 25 | 26 | ntsc_burst_v = -round(ntsc_burst_amplitude * math.sin(math.radians(ntsc_burst_phase))) 27 | ntsc_burst_u = -round(ntsc_burst_amplitude * math.cos(math.radians(ntsc_burst_phase))) 28 | 29 | 30 | def sine_frequency_to_increment(freq): 31 | dds_accu_width = 51 32 | dds_wrap = 2 ** dds_accu_width 33 | return dds_wrap / (system_clock / freq) 34 | 35 | 36 | phase_increment_db = round(sine_frequency_to_increment(secam_db_frequency)) 37 | phase_increment_dr = round(sine_frequency_to_increment(secam_dr_frequency)) 38 | phase_increment_pal = round(sine_frequency_to_increment(official_pal_frequency)) 39 | phase_increment_ntsc = round(sine_frequency_to_increment(official_ntsc_frequency)) 40 | 41 | print(f"Calculated pal increment would be {phase_increment_pal}") 42 | print(f"Calculated db increment would be {phase_increment_db}") 43 | print(f"Calculated dr increment would be {phase_increment_dr}") 44 | print( 45 | f"Possible increment of increment {round(sine_frequency_to_increment(secam_db_frequency + secam_db_hub)) - round(sine_frequency_to_increment(secam_db_frequency))}") 46 | print(f"Max hub of dB {round(sine_frequency_to_increment(secam_db_hub)) >> 39} ") 47 | print(f"Max hub of dR {round(sine_frequency_to_increment(secam_dr_hub)) >> 39} ") 48 | 49 | 50 | def print_filter(prefix, filter): 51 | print(f"{prefix} B {filter.b}") 52 | print(f"{prefix} A {filter.a}") 53 | 54 | for idx, value in enumerate(filter.b): 55 | file.write(f"`define {prefix}_B{idx} {filter.b[idx]}\n") 56 | 57 | for idx, value in enumerate(filter.a): 58 | file.write(f"`define {prefix}_A{idx} {filter.a[idx]}\n") 59 | 60 | file.write(f"`define {prefix}_B_AFTER_DOT {filter.b_after_dot}\n") 61 | file.write(f"`define {prefix}_A_AFTER_DOT {filter.a_after_dot}\n\n") 62 | 63 | 64 | # Band pass filter that ensures that the chroma signal doesn't bleed into the luma frequency range during transitions 65 | def generate_chroma_filter(): 66 | b_after_dot = 5 67 | a_after_dot = 5 68 | 69 | band_start = official_pal_frequency - 1.1e6 70 | band_stop = official_pal_frequency + 1.7e6 71 | sos = signal.iirfilter(1, [band_start, band_stop], btype='bandpass', analog=False, ftype='bessel', fs=system_clock, 72 | output='sos') 73 | b, a = signal.sos2tf(sos) 74 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 75 | print_filter("PAL_CHROMA", fpfilter) 76 | 77 | band_start = official_ntsc_frequency - 1.1e6 78 | band_stop = official_ntsc_frequency + 1.7e6 79 | sos = signal.iirfilter(1, [band_start, band_stop], btype='bandpass', analog=False, ftype='bessel', fs=system_clock, 80 | output='sos') 81 | b, a = signal.sos2tf(sos) 82 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 83 | print_filter("NTSC_CHROMA", fpfilter) 84 | 85 | 86 | # Protects the chroma signal from high frequencies in the luma signal 87 | def generate_pal_luma_filter(): 88 | luma_stop = official_pal_frequency - 2e6 89 | print(f"luma_stop {luma_stop}") 90 | b_after_dot = 10 91 | a_after_dot = 10 92 | sos = signal.bessel(2, luma_stop, 'low', analog=False, norm='phase', fs=system_clock, output='sos') 93 | b, a = signal.sos2tf(sos) 94 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 95 | print_filter("PAL_LUMA_LOWPASS", fpfilter) 96 | 97 | 98 | # A SECAM chroma lowpass is described in the standard but I have the feeling 99 | # that it is not necessary. 100 | def generate_secam_chroma_lowpass_filter(): 101 | b_after_dot = 11 102 | a_after_dot = 8 103 | # whether 3.0 MHz or 1.8MHz shows no difference 104 | sos = signal.bessel(1, 1.8e6, 'lp', fs=system_clock, output='sos') 105 | b, a = signal.sos2tf(sos) 106 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 107 | print_filter("SECAM_CHROMA_LOWPASS", fpfilter) 108 | 109 | 110 | # I have to be honest here. I don't understand what I'm doing here. 111 | # There is no reference in the SECAM standard that a low pass of the amplitude is 112 | # even required. But still It improves the picture, so I'm doing that. 113 | def generate_secam_amplitude_lowpass_filter(): 114 | b_after_dot = 11 115 | a_after_dot = 8 116 | sos = signal.bessel(1, 0.5e6, 'lp', fs=system_clock, output='sos') 117 | b, a = signal.sos2tf(sos) 118 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 119 | print_filter("SECAM_AMPLITUDE_LOWPASS", fpfilter) 120 | 121 | 122 | # I have to be honest here. I don't understand what I'm doing here. 123 | # This filter shall simulate the deemphasis inside the SECAM receiver. 124 | def generate_secam_preemphasis(): 125 | b_after_dot = 11 126 | a_after_dot = 11 127 | sos = signal.bessel(1, 0.1e6, 'lp', fs=system_clock, output='sos') 128 | b, a = signal.sos2tf(sos) 129 | fpfilter = FpFilter(b, a, b_after_dot, a_after_dot) 130 | print_filter("SECAM_PREEMPHASIS", fpfilter) 131 | 132 | 133 | # I have to be honest here. I don't understand what I'm doing here. 134 | # The HF preemphasis provides a square function which, if implemented 135 | # according to the standard looks bad. 136 | # But I assume this correlates with the low pass filter that I use after the 8 bit DAC for smoothing the signals. 137 | def frequency_to_amplitude(x): 138 | center = 4.286 139 | x = x / 1000000 140 | if x < center: 141 | # 120 gives a more stable purple than 60 142 | y = 120 * (center - x) * (center - x) 143 | else: 144 | # 60 gives better results than 90 145 | # 90 gives a negative luma dent in blue 146 | y = 60 * (center - x) * (center - x) 147 | 148 | y = y + 5 149 | 150 | if y > 31: 151 | y = 31 152 | # y=10 153 | # assert y>0 154 | assert abs(y) <= 31, f"{y}" 155 | 156 | return y 157 | 158 | 159 | def increment_to_sine_frequency(increment): 160 | dds_accu_width = 51 161 | dds_wrap = 2 ** dds_accu_width 162 | return increment * system_clock / dds_wrap 163 | 164 | 165 | # print(increment_to_sine_frequency(255911800489825)) 166 | # exit(0) 167 | 168 | def build_frequency_to_amplitude_lut(): 169 | start_inc = round(sine_frequency_to_increment(3.0e6)) >> 36 170 | end_inc = round(sine_frequency_to_increment(5.8e6)) >> 36 171 | print(start_inc) 172 | print(end_inc) 173 | 174 | with open("../mem/secam_ampl.txt", "w") as file: 175 | for inc in range(start_inc, end_inc): 176 | freq = increment_to_sine_frequency(inc << 36) 177 | file.write(f"{hex(round(frequency_to_amplitude(freq)))[2:]}\n") 178 | 179 | 180 | def generate_sine_lut(): 181 | phase_width = 5 182 | amplitude_width = 6 183 | 184 | phase_count = 2 ** phase_width 185 | phase_max = phase_count - 1 186 | amplitude_max = 2 ** amplitude_width - 1 187 | 188 | print(phase_max) 189 | print(amplitude_max) 190 | 191 | with open("../mem/sinewave.txt", "w") as f: 192 | for a in range(0, amplitude_max + 1): 193 | for p in range(0, phase_max + 1): 194 | a_scaled = a * 2 195 | value = round(a_scaled * math.sin(p * math.pi * 2 / phase_count)) 196 | # Rather evil trick to convert signed to unsigned 197 | value = value & 0xff 198 | f.write(f"{value:02x}\n") 199 | 200 | 201 | def generate(): 202 | global file 203 | with open("../rtl/coefficients.svh", "w") as file: 204 | generate_sine_lut() 205 | generate_chroma_filter() 206 | generate_pal_luma_filter() 207 | generate_secam_amplitude_lowpass_filter() 208 | generate_secam_chroma_lowpass_filter() 209 | generate_secam_preemphasis() 210 | build_frequency_to_amplitude_lut() 211 | 212 | file.write(f"`define CLK_PERIOD_USEC {1e6 / system_clock} // .8\n\n") 213 | 214 | file.write(f"`define SECAM_CHROMA_DB_DDS_INCREMENT 51'd{phase_increment_db}\n") 215 | file.write(f"`define SECAM_CHROMA_DR_DDS_INCREMENT 51'd{phase_increment_dr}\n") 216 | file.write(f"`define PAL_CHROMA_DDS_INCREMENT 51'd{phase_increment_pal}\n") 217 | file.write(f"`define NTSC_CHROMA_DDS_INCREMENT 51'd{phase_increment_ntsc}\n\n") 218 | 219 | file.write(f"`define PAL_BURST_U {pal_burst_u}\n") 220 | file.write(f"`define PAL_BURST_V {pal_burst_v}\n") 221 | file.write(f"`define NTSC_BURST_U {ntsc_burst_u}\n") 222 | file.write(f"`define NTSC_BURST_V {ntsc_burst_v}\n\n") 223 | 224 | 225 | if __name__ == '__main__': 226 | generate() 227 | -------------------------------------------------------------------------------- /tools/debugcom.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import struct 4 | 5 | import serial 6 | import serial.threaded 7 | 8 | 9 | def set_bit(v, index, x): 10 | """Set the index:th bit of v to 1 if x is truthy, else to 0, and return the new value.""" 11 | mask = 1 << index # Compute mask, an integer with just bit 'index' set. 12 | v &= ~mask # Clear the bit indicated by the mask (if x is False) 13 | if x: 14 | v |= mask # If x was True, set the bit indicated by the mask. 15 | return v # Return the result, we're done. 16 | 17 | 18 | class DebugCom: 19 | def __init__(self): 20 | self.serial = serial.Serial() 21 | self.serial.port = "/dev/serial/by-id/usb-SIPEED_JTAG_Debugger_FactoryAIOT_Pro-if01-port0" 22 | self.serial.baudrate = 3000000 23 | self.serial.timeout = 1 24 | self.serial.open() 25 | 26 | # TODO For some reason the serial port only works after the second open. 27 | # This problem correlates with the baud rate. With 115200 I don't have this issue 28 | # This might be a linux only problem? Requires check on another operation system. 29 | self.serial.close() 30 | self.serial.open() 31 | 32 | self.config_flags = 0 33 | self.enable_chroma_output(True) 34 | 35 | def memwrite_u8(self, addr, val): 36 | command = struct.pack(">cHB", b'W', addr, val) 37 | self.serial.write(command) 38 | readback = self.serial.read() 39 | assert (readback == b'K') 40 | 41 | def blockmemwrite_u8(self, addr, data): 42 | assert len(data) <= 256 43 | command = struct.pack(">cBH", b'B', len(data) - 1, addr) 44 | self.serial.write(command) 45 | 46 | for value in data: 47 | command = struct.pack("B", value) 48 | self.serial.write(command) 49 | 50 | readback = self.serial.read() 51 | assert (readback == b'K') 52 | 53 | def memwrite_u16be(self, addr, val: int): 54 | self.memwrite_u8(addr, (val >> 8) & 0xff) 55 | self.memwrite_u8(addr + 1, (val >> 0) & 0xff) 56 | 57 | def memwrite_u32be(self, addr, val): 58 | self.memwrite_u8(addr, (val >> 24) & 0xff) 59 | self.memwrite_u8(addr + 1, (val >> 16) & 0xff) 60 | self.memwrite_u8(addr + 2, (val >> 8) & 0xff) 61 | self.memwrite_u8(addr + 3, (val >> 0) & 0xff) 62 | 63 | def memwrite_s8(self, addr, val): 64 | command = struct.pack(">cHb", b'W', addr, val) 65 | self.serial.write(command) 66 | readback = self.serial.read() 67 | assert (readback == b'K') 68 | 69 | def memread(self, addr): 70 | command = struct.pack(">cH", b'R', addr) 71 | self.serial.write(command) 72 | arr = self.serial.read() 73 | return struct.unpack("B", arr)[0] 74 | 75 | def configure_video_standard(self, standard, video_device=None): 76 | number_of_lines_50_hz = 312 77 | number_of_lines_60_hz = 262 78 | number_of_visible_lines_50_hz = 256 79 | number_of_visible_lines_60_hz = 200 80 | 81 | if standard == "SECAM": 82 | self.memwrite_s8(0x0001, 2) 83 | self.memwrite_u16be(2, number_of_lines_50_hz) 84 | self.memwrite_u16be(4, number_of_visible_lines_50_hz) 85 | elif standard == "NTSC": 86 | self.memwrite_s8(0x0001, 1) 87 | self.memwrite_u16be(2, number_of_lines_60_hz) 88 | self.memwrite_u16be(4, number_of_visible_lines_60_hz) 89 | elif standard == "PAL": 90 | self.memwrite_s8(0x0001, 0) 91 | self.memwrite_u16be(2, number_of_lines_50_hz) 92 | self.memwrite_u16be(4, number_of_visible_lines_50_hz) 93 | elif standard == "PAL60": 94 | self.memwrite_s8(0x0001, 0) 95 | self.memwrite_u16be(2, number_of_lines_60_hz) 96 | self.memwrite_u16be(4, number_of_visible_lines_60_hz) 97 | else: 98 | raise "Invalid argument provided!" 99 | 100 | if video_device: 101 | assert os.system(f"v4l2-ctl -d {video_device} -s {standard}") == 0 102 | 103 | def configure_framebuffer(self, width, lines_per_field, interlacing_mode, bits_per_pixel, clks_per_pixel=None): 104 | active_window_ticks = 3 * (768 + 16) 105 | if clks_per_pixel is None: 106 | clks_per_pixel = math.floor(active_window_ticks / width) 107 | 108 | height = lines_per_field * 2 if interlacing_mode else lines_per_field 109 | 110 | if bits_per_pixel == 24: 111 | stride = width * 3 / 4 112 | assert stride.is_integer() 113 | stride = int(stride) 114 | self.enable_convolver_passthrough(0) 115 | elif bits_per_pixel == 32: 116 | stride = width 117 | self.enable_convolver_passthrough(1) 118 | else: 119 | assert False, f"{bits_per_pixel} is not allowed. Either 24 or 32 bits per pixel!" 120 | 121 | # Move framebuffer away from calibration addresses 122 | even_field_addr = 0x500 123 | odd_field_addr = even_field_addr + stride 124 | 125 | # Double stride for interlacing so every other line is skipped 126 | # during field readout 127 | if interlacing_mode: 128 | stride = stride * 2 129 | 130 | self.memwrite_u16be(0x0300, width) # Width 131 | self.memwrite_u16be(0x0302, lines_per_field) # Height of a single field 132 | self.memwrite_u16be(0x0310, stride) # Stride 133 | self.memwrite_u32be(0x0306, even_field_addr) # Even field framebuffer start 134 | self.memwrite_u32be(0x030a, odd_field_addr) # Odd field framebuffer start 135 | self.memwrite_u8(0x0304, clks_per_pixel) # Clks per Pixel 136 | self.memwrite_u8(0x0305, 550 >> 2) # Window H Start in clocks 137 | self.memwrite_u8(0x030e, 30) # Window V Start in lines 138 | 139 | print(f"Framebuffer size {width} * {height} with {clks_per_pixel} clock ticks per pixel") 140 | print(f"Use a stride of {stride}") 141 | 142 | return height 143 | 144 | def set_delay_lines(self, y, u, v): 145 | # It should be unsigned instead of signed... 146 | # But having it signed allows a fast wrap around to check the maximum 147 | self.memwrite_s8(0, y) 148 | self.memwrite_s8(12, u) 149 | self.memwrite_s8(13, v) 150 | 151 | def set_ntsc_burst_uv(self, u, v): 152 | self.memwrite_s8(7, u) 153 | self.memwrite_s8(8, v) 154 | print(f"Set NTSC Burst {u} {v}") 155 | 156 | def set_secam_preemphasis_swing(self, db, dr): 157 | self.memwrite_s8(14, db) 158 | self.memwrite_s8(15, dr) 159 | print(f"Set SECAM Preemphasis Swing {db} {dr}") 160 | 161 | def set_secam_ampl_delay(self, ampl_delay): 162 | self.memwrite_s8(16, ampl_delay) 163 | print(f"Set SECAM Ampltidue Delay {ampl_delay}") 164 | 165 | def set_ntsc_burst(self, amplitude, phase): 166 | v = -round(amplitude * math.sin(math.radians(phase))) 167 | u = -round(amplitude * math.cos(math.radians(phase))) 168 | self.set_ntsc_burst_uv(u, v) 169 | 170 | def set_luma_black_level(self, y): 171 | self.memwrite_u8(9, y) 172 | 173 | def set_video_prescalers(self, standard, y, u, v): 174 | print(f"Set {standard} scalers to {y} {u} {v}") 175 | 176 | if standard == "PAL": 177 | self.memwrite_u8(0x0200 + 4 * 0 + 0, y) 178 | self.memwrite_u8(0x0200 + 4 * 1 + 0, u) 179 | self.memwrite_u8(0x0200 + 4 * 2 + 0, v) 180 | if standard == "NTSC": 181 | self.memwrite_u8(0x0200 + 4 * 0 + 1, y) 182 | self.memwrite_u8(0x0200 + 4 * 1 + 1, u) 183 | self.memwrite_u8(0x0200 + 4 * 2 + 1, v) 184 | if standard == "SECAM": 185 | self.memwrite_u8(0x0200 + 4 * 0 + 2, y) 186 | self.memwrite_u8(0x0200 + 4 * 1 + 2, u) 187 | self.memwrite_u8(0x0200 + 4 * 2 + 2, v) 188 | 189 | def enable_chroma_lowpass(self, v): 190 | self.config_flags = set_bit(self.config_flags, 0, v) 191 | self.memwrite_u8(6, self.config_flags) 192 | 193 | def enable_qam_chroma_bandpass(self, v): 194 | self.config_flags = set_bit(self.config_flags, 1, v) 195 | self.memwrite_u8(6, self.config_flags) 196 | 197 | def enable_single_line_mode(self, v): 198 | self.config_flags = set_bit(self.config_flags, 2, v) 199 | self.memwrite_u8(6, self.config_flags) 200 | 201 | def enable_internal_colorbars(self, v): 202 | self.config_flags = set_bit(self.config_flags, 3, v) 203 | self.memwrite_u8(6, self.config_flags) 204 | 205 | def enable_chroma_output(self, v): 206 | self.config_flags = set_bit(self.config_flags, 4, v) 207 | self.memwrite_u8(6, self.config_flags) 208 | 209 | def enable_interlacing(self, v): 210 | self.config_flags = set_bit(self.config_flags, 5, v) 211 | self.memwrite_u8(6, self.config_flags) 212 | 213 | def enable_rgb_mode(self, v): 214 | self.config_flags = set_bit(self.config_flags, 6, v) 215 | self.memwrite_u8(6, self.config_flags) 216 | 217 | def enable_convolver_passthrough(self, v): 218 | self.config_flags = set_bit(self.config_flags, 7, v) 219 | self.memwrite_u8(6, self.config_flags) 220 | 221 | def logic_analyzer_read(self): 222 | status = self.memread(0x100) 223 | if (status & 1): 224 | print("Trigger activated!") 225 | else: 226 | print("Collecting...") 227 | 228 | print(f"Measurement {self.memread(0x101)}") 229 | position = status >> 1 230 | print(position) 231 | 232 | for j in range(64): 233 | i = (j + position) % 64 234 | 235 | value3 = self.memread(i * 4 + 0) 236 | value2 = self.memread(i * 4 + 1) 237 | value1 = self.memread(i * 4 + 2) 238 | value0 = self.memread(i * 4 + 3) 239 | 240 | value3 = bin(value3)[2:].zfill(8) 241 | value2 = bin(value2)[2:].zfill(8) 242 | value1 = bin(value1)[2:].zfill(8) 243 | value0 = bin(value0)[2:].zfill(8) 244 | 245 | # print(i,value3,value2,value1,value0) 246 | print(f"{value3} {value2} {value1} {value0}") 247 | -------------------------------------------------------------------------------- /tools/debugcom_hil_all.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from debugcom_hil_ebu75 import test_colorbars 4 | from debugcom_hil_parrot import test_parrot 5 | from debugcom_hil_secam import test_secam_stress 6 | 7 | 8 | def upload_fpga_core(): 9 | os.system('../gowin/upload.sh') 10 | 11 | 12 | if __name__ == '__main__': 13 | upload_fpga_core() 14 | test_colorbars() 15 | upload_fpga_core() 16 | test_parrot() 17 | upload_fpga_core() 18 | test_secam_stress() 19 | print("I have finished the tests!") 20 | -------------------------------------------------------------------------------- /tools/debugcom_hil_ebu75.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | import color 5 | from debugcom import DebugCom 6 | from defaults import set_default_scalers 7 | from framebuffer import transfer_picture 8 | from v4l import video_device, capture_still_frame 9 | 10 | debugcom = DebugCom() 11 | 12 | 13 | def construct_ebu_reference(l): 14 | reference = [] 15 | 16 | reference.append(color.rgb2ycbcr(l, l, l)) # White 17 | reference.append(color.rgb2ycbcr(l, l, 0)) # Yellow 18 | reference.append(color.rgb2ycbcr(0, l, l)) # Cyan 19 | reference.append(color.rgb2ycbcr(0, l, 0)) # Green 20 | reference.append(color.rgb2ycbcr(l, 0, l)) # Purple 21 | reference.append(color.rgb2ycbcr(l, 0, 0)) # Red 22 | reference.append(color.rgb2ycbcr(0, 0, l)) # Blue 23 | reference.append(color.rgb2ycbcr(0, 0, 0)) # Black 24 | 25 | return reference 26 | 27 | 28 | def construct_ebu_scanline(l): 29 | imga = np.zeros([2, 256, 3], dtype=np.uint8) 30 | 31 | y = 0 32 | imga[y, 32 * 0:32 * 1] = [l, l, l] 33 | imga[y, 32 * 1:32 * 2] = [0, l, l] 34 | imga[y, 32 * 2:32 * 3] = [l, l, 0] 35 | imga[y, 32 * 3:32 * 4] = [0, l, 0] 36 | imga[y, 32 * 4:32 * 5] = [l, 0, l] 37 | imga[y, 32 * 5:32 * 6] = [0, 0, l] 38 | imga[y, 32 * 6:32 * 7] = [l, 0, 0] 39 | imga[y, 32 * 7:32 * 8] = [0, 0, 0] 40 | return imga 41 | 42 | 43 | def construct_ebu75(): 44 | return construct_ebu_scanline(191) 45 | 46 | 47 | def construct_ebu100(): 48 | return construct_ebu_scanline(255) 49 | 50 | 51 | def grab_and_check_ebu75(videonorm, rgb_value): 52 | debugcom.configure_video_standard(videonorm, video_device=video_device) 53 | frame = capture_still_frame()[50:200, :] 54 | 55 | kernel = np.ones((5, 5), np.float32) / 25 56 | frame_avg = cv2.filter2D(frame, -1, kernel) 57 | white = list(np.flip(frame_avg[70, 90])) 58 | yellow = list(np.flip(frame_avg[70, 161])) 59 | cyan = list(np.flip(frame_avg[70, 244])) 60 | green = list(np.flip(frame_avg[70, 320])) 61 | purple = list(np.flip(frame_avg[70, 406])) 62 | red = list(np.flip(frame_avg[70, 484])) 63 | blue = list(np.flip(frame_avg[70, 569])) 64 | black = list(np.flip(frame_avg[70, 633])) 65 | # R G B 66 | 67 | results_rgb = [white, yellow, cyan, green, purple, red, blue, black] 68 | results_ycbcr = [color.rgb2ycbcr(x[0], x[1], x[2]) for x in results_rgb] 69 | results_deviation = [] 70 | reference_ycbcr = construct_ebu_reference(rgb_value) 71 | names = ["White", "Yellow", "Cyan", "Green", "Purple", "Red", "Blue", "Black"] 72 | 73 | print(f"{videonorm:>8}: | RsR RsG RsB | ResY ResU ResV | RefY RefU RefV | DevY DevU DevV |") 74 | 75 | u_dev_list = [] 76 | v_dev_list = [] 77 | 78 | for i in range(len(results_rgb)): 79 | y_dev = results_ycbcr[i][0] - reference_ycbcr[i][0] 80 | u_dev = results_ycbcr[i][1] - reference_ycbcr[i][1] 81 | v_dev = results_ycbcr[i][2] - reference_ycbcr[i][2] 82 | u_dev_list.append(abs(u_dev)) 83 | v_dev_list.append(abs(v_dev)) 84 | 85 | results_deviation.append((y_dev, u_dev, v_dev)) 86 | print(f"{names[i]:>9} | " 87 | f"{results_rgb[i][0]:>3} {results_rgb[i][1]:>3} {results_rgb[i][2]:>3} | " 88 | f"{results_ycbcr[i][0]:>4} {results_ycbcr[i][1]:>4} {results_ycbcr[i][2]:>4} | " 89 | f"{reference_ycbcr[i][0]:>4} {reference_ycbcr[i][1]:>4} {reference_ycbcr[i][2]:>4} | " 90 | f"{results_deviation[i][0]:>4} {results_deviation[i][1]:>4} {results_deviation[i][2]:>4} | ") 91 | 92 | u_dev_avg = sum(u_dev_list) / len(u_dev_list) 93 | v_dev_avg = sum(v_dev_list) / len(v_dev_list) 94 | 95 | print(" " 96 | f"{round(u_dev_avg):>4} {round(v_dev_avg):>4}") 97 | 98 | return frame, results_rgb 99 | 100 | 101 | def test_colorbars(): 102 | set_default_scalers(debugcom) 103 | interlacing_mode = False 104 | rgb_mode = False 105 | clks_per_pixel = 9 106 | width = 256 107 | lines_per_field = 256 108 | height = debugcom.configure_framebuffer(width, lines_per_field, interlacing_mode, 32, clks_per_pixel) 109 | debugcom.enable_single_line_mode(True) 110 | debugcom.enable_qam_chroma_bandpass(True) 111 | debugcom.enable_chroma_output(True) 112 | debugcom.enable_chroma_lowpass(False) 113 | debugcom.enable_interlacing(interlacing_mode) 114 | debugcom.enable_rgb_mode(rgb_mode) 115 | 116 | print("-------------------- 100% --------------------") 117 | imga = construct_ebu100() 118 | transfer_picture(debugcom, imga, rgb_mode) 119 | ntsc_frame, ntsc_result_100 = grab_and_check_ebu75("NTSC", 255) 120 | pal_frame, pal_result_100 = grab_and_check_ebu75("PAL", 255) 121 | debugcom.set_delay_lines(0, 0, 3) 122 | secam_frame, secam_result_100 = grab_and_check_ebu75("SECAM", 255) 123 | debugcom.set_delay_lines(0, 0, 0) 124 | ebu100concat = cv2.vconcat([ntsc_frame, pal_frame, secam_frame]) 125 | print("-------------------- 75% --------------------") 126 | imga = construct_ebu75() 127 | transfer_picture(debugcom, imga, rgb_mode) 128 | ntsc_frame, ntsc_result_75 = grab_and_check_ebu75("NTSC", 191) 129 | pal_frame, pal_result_75 = grab_and_check_ebu75("PAL", 191) 130 | debugcom.set_delay_lines(0, 0, 3) 131 | secam_frame, secam_result_75 = grab_and_check_ebu75("SECAM", 191) 132 | debugcom.set_delay_lines(0, 0, 0) 133 | ebu75concat = cv2.vconcat([ntsc_frame, pal_frame, secam_frame]) 134 | 135 | print("----- 75% -----") 136 | print(f"NTSC {ntsc_result_75}") 137 | print(f"PAL {pal_result_75}") 138 | print(f"SECAM {secam_result_75}") 139 | print("----- 100% -----") 140 | print(f"NTSC {ntsc_result_100}") 141 | print(f"PAL {pal_result_100}") 142 | print(f"SECAM {secam_result_100}") 143 | print("----- ---- -----") 144 | 145 | cv2.imwrite(f"../doc/ebu75.png", ebu75concat) 146 | cv2.imwrite(f"../doc/ebu100.png", ebu100concat) 147 | 148 | concat = cv2.vconcat([ebu75concat, ebu100concat]) 149 | 150 | return concat 151 | 152 | 153 | if __name__ == '__main__': 154 | concat = test_colorbars() 155 | 156 | # Display the resulting frame 157 | cv2.imshow("preview", concat) 158 | 159 | # Waits for a user input to quit the application 160 | cv2.waitKey(0) 161 | cv2.destroyAllWindows() 162 | -------------------------------------------------------------------------------- /tools/debugcom_hil_parrot.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | from debugcom import DebugCom 4 | from framebuffer import transfer_picture, framebuffer_easy_conf 5 | from v4l import video_device, capture_still_frame 6 | 7 | debugcom = DebugCom() 8 | 9 | ntsc_burst_amplitude = 10 10 | phase = -45 + 33 11 | 12 | 13 | def grab_and_store(videonorm): 14 | debugcom.configure_video_standard(videonorm, video_device=video_device) 15 | frame = capture_still_frame() 16 | cv2.imwrite(f"../doc/parrot_{videonorm.lower()}.png", frame) 17 | 18 | 19 | def test_parrot(): 20 | interlacing_mode = True 21 | rgb_mode = True 22 | width = 768 + 16 23 | filename = "../doc/parrot.jpg" 24 | img = cv2.imread(filename) 25 | 26 | # Start with PAL 27 | videonorm = "PAL" 28 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, overscan=15) 29 | print("Resizing...") 30 | resized = cv2.resize(img, dsize=(width, height), interpolation=cv2.INTER_AREA) 31 | 32 | # Perform a vertical blur for interlaced video to 33 | # avoid flickering hard edges. I'm using a gaussian blur here 34 | # with slightly modified sigma to avoid destroying too much detail. 35 | if interlacing_mode: 36 | blurred = cv2.GaussianBlur(resized, (1, 3), 0.6) 37 | 38 | transfer_picture(debugcom, blurred, rgb_mode) 39 | grab_and_store(videonorm) 40 | 41 | # Continue with SECAM as the resolution is the same 42 | videonorm = "SECAM" 43 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, overscan=15) 44 | grab_and_store(videonorm) 45 | 46 | # Then do NTSC as the resolution is different 47 | videonorm = "NTSC" 48 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, overscan=15) 49 | grab_and_store(videonorm) 50 | 51 | print("Resizing...") 52 | resized = cv2.resize(img, dsize=(width, height), interpolation=cv2.INTER_AREA) 53 | if interlacing_mode: 54 | blurred = cv2.GaussianBlur(resized, (1, 3), 0.6) 55 | transfer_picture(debugcom, blurred, rgb_mode) 56 | grab_and_store(videonorm) 57 | 58 | 59 | if __name__ == '__main__': 60 | test_parrot() 61 | -------------------------------------------------------------------------------- /tools/debugcom_hil_secam.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | from debugcom import DebugCom 4 | from framebuffer import transfer_picture, framebuffer_easy_conf 5 | from v4l import video_device, capture_still_frame 6 | 7 | debugcom = DebugCom() 8 | 9 | ntsc_burst_amplitude = 10 10 | phase = -45 + 33 11 | 12 | 13 | def grab_and_store(videonorm): 14 | debugcom.configure_video_standard(videonorm, video_device=video_device) 15 | frame = capture_still_frame() 16 | cv2.imwrite(f"../doc/secam_stresstest_result.png", frame) 17 | return frame 18 | 19 | 20 | def test_secam_stress(): 21 | interlacing_mode = False 22 | rgb_mode = True 23 | width = 256 24 | filename = "../doc/secam_stresstest.png" 25 | img = cv2.imread(filename) 26 | 27 | videonorm = "SECAM" 28 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, overscan=0) 29 | transfer_picture(debugcom, img, rgb_mode) 30 | frame = grab_and_store(videonorm) 31 | 32 | return frame 33 | 34 | 35 | if __name__ == '__main__': 36 | frame = test_secam_stress() 37 | 38 | # Display the resulting frame 39 | cv2.imshow("preview", frame) 40 | # Waits for a user input to quit the application 41 | cv2.waitKey(0) 42 | cv2.destroyAllWindows() 43 | -------------------------------------------------------------------------------- /tools/debugcom_imageviewer.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | from debugcom import DebugCom 4 | from framebuffer import transfer_picture, framebuffer_easy_conf 5 | 6 | 7 | def resize_and_transfer_picture(): 8 | videonorm = "PAL" 9 | interlacing_mode = True 10 | rgb_mode = True 11 | width = 768 + 16 12 | bits_per_pixel = 24 13 | 14 | debugcom = DebugCom() 15 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, bits_per_pixel, overscan=15) 16 | filename = "../doc/parrot.jpg" 17 | img = cv2.imread(filename) 18 | print("Resizing...") 19 | img = cv2.resize(img, dsize=(width, height), interpolation=cv2.INTER_AREA) 20 | 21 | # Perform a vertical blur for interlaced video to 22 | # avoid flickering hard edges. I'm using a gaussian blur here 23 | # with slightly modified sigma to avoid destroying too much detail. 24 | if interlacing_mode: 25 | img = cv2.GaussianBlur(img, (1, 3), 0.6) 26 | 27 | transfer_picture(debugcom, img, rgb_mode, bits_per_pixel == 32) 28 | 29 | 30 | if __name__ == '__main__': 31 | resize_and_transfer_picture() 32 | -------------------------------------------------------------------------------- /tools/debugcom_interactive.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import cv2 4 | 5 | from debugcom import DebugCom 6 | from debugcom_hil_ebu75 import construct_ebu75 7 | from defaults import set_default_scalers 8 | from framebuffer import transfer_picture, framebuffer_easy_conf 9 | from getch import getch 10 | 11 | debugcom = DebugCom() 12 | 13 | 14 | def interactive_configurator(single_line=False, interlacing_mode=False, rgb_mode=False, secam_focused=False, 15 | internal_colorbars=False): 16 | ntsc_burst_amplitude = 10 17 | ntsc_burst_phase = 0 18 | luma_black_level = 47 19 | luma_scaler = 125 20 | ampl_delay = 0 21 | 22 | chroma_lowpass = False 23 | qam_bandpass = False 24 | debugcom.enable_single_line_mode(single_line) 25 | debugcom.enable_internal_colorbars(internal_colorbars) 26 | debugcom.enable_interlacing(interlacing_mode) 27 | debugcom.enable_rgb_mode(rgb_mode) 28 | 29 | y_delay = 0 30 | u_delay = 0 31 | v_delay = 0 32 | 33 | # ampl_delay 4 u_delay 0 v_delay 7 secam_db_swing 56 secam_dr_swing 32 34 | 35 | secam_db_swing = 29 36 | secam_dr_swing = 17 37 | 38 | while True: 39 | ntsc_burst_v = -round(ntsc_burst_amplitude * math.sin(math.radians(ntsc_burst_phase))) 40 | ntsc_burst_u = -round(ntsc_burst_amplitude * math.cos(math.radians(ntsc_burst_phase))) 41 | debugcom.set_delay_lines(y_delay, u_delay, v_delay) 42 | debugcom.set_luma_black_level(luma_black_level) 43 | debugcom.set_ntsc_burst_uv(ntsc_burst_u, ntsc_burst_v) 44 | debugcom.set_secam_preemphasis_swing(secam_db_swing, secam_dr_swing) 45 | debugcom.set_secam_ampl_delay(ampl_delay) 46 | 47 | if secam_focused: 48 | print( 49 | f"ampl_delay {ampl_delay} u_delay {u_delay} v_delay {v_delay} secam_db_swing {secam_db_swing} secam_dr_swing {secam_dr_swing}") 50 | else: 51 | print(ntsc_burst_amplitude, ntsc_burst_phase, ntsc_burst_u, ntsc_burst_v, luma_black_level, luma_scaler) 52 | 53 | char = getch() 54 | if char == "q": 55 | exit(0) 56 | 57 | if secam_focused: 58 | if char == "w": 59 | secam_db_swing += 1 60 | if char == "s": 61 | secam_db_swing -= 1 62 | if char == 'e': 63 | secam_dr_swing += 1 64 | if char == 'd': 65 | secam_dr_swing -= 1 66 | 67 | if char == "+": 68 | ampl_delay += 1 69 | print(f"ampl_delay {ampl_delay}") 70 | if char == "-": 71 | ampl_delay -= 1 72 | print(f"ampl_delay {ampl_delay}") 73 | else: 74 | if char == "w": 75 | ntsc_burst_amplitude += 1 76 | if char == "s": 77 | ntsc_burst_amplitude -= 1 78 | if char == 'e': 79 | ntsc_burst_phase += 1 80 | if ntsc_burst_phase > 180: 81 | ntsc_burst_phase -= 360 82 | if char == 'd': 83 | ntsc_burst_phase -= 1 84 | if ntsc_burst_phase < -180: 85 | ntsc_burst_phase += 360 86 | 87 | if char == "+": 88 | y_delay += 1 89 | print(f"y_delay {y_delay}") 90 | if char == "-": 91 | y_delay -= 1 92 | print(f"y_delay {y_delay}") 93 | 94 | if char == 'r': 95 | luma_black_level += 1 96 | if char == 'f': 97 | luma_black_level -= 1 98 | if char == 't': 99 | luma_scaler += 1 100 | if char == 'g': 101 | luma_scaler -= 1 102 | if char == 'z': 103 | chroma_lowpass = not chroma_lowpass 104 | debugcom.enable_chroma_lowpass(chroma_lowpass) 105 | if char == 'u': 106 | qam_bandpass = not qam_bandpass 107 | debugcom.enable_qam_chroma_bandpass(qam_bandpass) 108 | if char == 'h': 109 | internal_colorbars = not internal_colorbars 110 | debugcom.enable_internal_colorbars(internal_colorbars) 111 | 112 | if char == "2": 113 | u_delay += 1 114 | print(f"u_delay {u_delay}") 115 | if char == "1": 116 | u_delay -= 1 117 | print(f"u_delay {u_delay}") 118 | 119 | if char == "4": 120 | v_delay += 1 121 | print(f"v_delay {v_delay}") 122 | if char == "3": 123 | v_delay -= 1 124 | print(f"v_delay {v_delay}") 125 | 126 | if char == '6': 127 | debugcom.configure_video_standard("PAL") 128 | if char == '7': 129 | debugcom.configure_video_standard("NTSC") 130 | if char == '8': 131 | debugcom.configure_video_standard("SECAM") 132 | 133 | 134 | def ebu75_interactive(): 135 | interlacing_mode = False 136 | rgb_mode = False 137 | clks_per_pixel = 9 138 | width = 256 139 | lines_per_field = 256 140 | height = debugcom.configure_framebuffer(width, lines_per_field, interlacing_mode, 32, clks_per_pixel) 141 | 142 | set_default_scalers(debugcom) 143 | debugcom.configure_video_standard("SECAM") 144 | imga = construct_ebu75() 145 | transfer_picture(debugcom, imga, rgb_mode) 146 | interactive_configurator(single_line=True, interlacing_mode=interlacing_mode, rgb_mode=rgb_mode) 147 | 148 | 149 | def secam_stresstest_interactive(): 150 | videonorm = "SECAM" 151 | interlacing_mode = False 152 | rgb_mode = False 153 | width = 256 154 | 155 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width) 156 | filename = "../doc/secam_stresstest.png" 157 | img = cv2.imread(filename) 158 | transfer_picture(debugcom, img, rgb_mode) 159 | interactive_configurator(interlacing_mode=interlacing_mode, rgb_mode=rgb_mode, secam_focused=True) 160 | 161 | 162 | def parrot_interactive(): 163 | videonorm = "SECAM" 164 | interlacing_mode = True 165 | rgb_mode = True 166 | width = 768 + 16 167 | 168 | height = framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width) 169 | filename = "../doc/parrot.jpg" 170 | img = cv2.imread(filename) 171 | print("Resizing...") 172 | img = cv2.resize(img, dsize=(width, height), interpolation=cv2.INTER_AREA) 173 | img = cv2.GaussianBlur(img, (1, 3), 0.6) 174 | transfer_picture(debugcom, img, rgb_mode) 175 | interactive_configurator(interlacing_mode=interlacing_mode, rgb_mode=rgb_mode, secam_focused=True) 176 | 177 | 178 | def internal_colorbars_interactive(): 179 | set_default_scalers(debugcom) 180 | interactive_configurator(single_line=True, secam_focused=True, internal_colorbars=True) 181 | 182 | 183 | if __name__ == '__main__': 184 | # ebu75_interactive() 185 | # internal_colorbars_interactive() 186 | # parrot_interactive() 187 | secam_stresstest_interactive() 188 | -------------------------------------------------------------------------------- /tools/defaults.py: -------------------------------------------------------------------------------- 1 | import color 2 | from debugcom import DebugCom 3 | 4 | 5 | def set_default_scalers(debugcom: DebugCom): 6 | ntsc_burst_amplitude = 14 7 | # This should be 0 degree. But for some reason 8 | # my USB video grabber has some phase issues 9 | # On my TV, 0 works and -17 is totally wrong. 10 | ntsc_burst_phase = -17 11 | debugcom.set_ntsc_burst(ntsc_burst_amplitude, ntsc_burst_phase) 12 | secam_db_swing = 33 13 | secam_dr_swing = 20 14 | debugcom.set_secam_preemphasis_swing(secam_db_swing, secam_dr_swing) 15 | debugcom.set_secam_ampl_delay(0) 16 | debugcom.set_luma_black_level(47) 17 | 18 | # Might look dark on Commodore 1084 but on the USB videograbber these values 19 | # are suitable for 75% and 100% color bars. 20 | _, u_scale, v_scale = color.ypbpr2yuv(0, 47, 47) 21 | debugcom.set_video_prescalers("PAL", 100, round(u_scale), round(v_scale)) 22 | debugcom.set_video_prescalers("NTSC", 100, round(u_scale), round(v_scale)) 23 | _, u_scale, v_scale = color.ypbpr2yuv(0, 41, 39) 24 | debugcom.set_video_prescalers("SECAM", 100, round(u_scale), round(v_scale)) 25 | 26 | debugcom.enable_qam_chroma_bandpass(True) 27 | debugcom.enable_chroma_output(True) 28 | -------------------------------------------------------------------------------- /tools/filterutil.py: -------------------------------------------------------------------------------- 1 | class FpFilter: 2 | def __init__(self, b, a, b_after_dot, a_after_dot): 3 | print(b) 4 | print(a) 5 | 6 | self.b = [int(round(x * 2 ** b_after_dot)) for x in b] 7 | self.a = [int(round(x * 2 ** a_after_dot)) for x in a] 8 | self.b_after_dot = b_after_dot 9 | self.a_after_dot = a_after_dot 10 | 11 | print(f"B {self.b}") 12 | print(f"A {self.a}") 13 | 14 | self.rs = [int(0)] * 8 15 | self.ls = [int(0)] * 8 16 | 17 | self.rs_total_or = [int(0)] * 8 18 | self.ls_total_or = [int(0)] * 8 19 | self.rounding = True 20 | 21 | def print_bit_usage(self): 22 | print(f"ls usage {[bin(x) for x in self.ls_total_or]}") 23 | print(f"ls usage {[x.bit_length() for x in self.ls_total_or]}") 24 | print(f"rs usage {[bin(x) for x in self.rs_total_or]}") 25 | print(f"rs usage {[x.bit_length() for x in self.rs_total_or]}") 26 | 27 | def filter_list(self, list): 28 | return [self.filter(int(x)) for x in list] 29 | 30 | def reduce(self, value, shift): 31 | if shift == 0: 32 | return int(value) 33 | 34 | if self.rounding: 35 | return (int(value) + (1 << (shift - 1))) >> shift 36 | else: 37 | return (int(value)) >> shift 38 | 39 | def filter(self, in_val): 40 | v = int(self.rs[0] + int(in_val)) 41 | y = self.ls[0] + self.reduce(int(self.b[0]) * v, self.b_after_dot) 42 | for i in range(len(self.b) - 1): 43 | self.rs[i] = int(self.reduce(-self.a[i + 1] * v, self.a_after_dot) + self.rs[i + 1]) 44 | self.ls[i] = int(self.reduce(self.b[i + 1] * v, self.b_after_dot) + self.ls[i + 1]) 45 | 46 | if self.rs[i] >= 0: 47 | self.rs_total_or[i] |= self.rs[i] 48 | else: 49 | self.rs_total_or[i] |= -self.rs[i] 50 | 51 | if self.ls[i] >= 0: 52 | self.ls_total_or[i] |= self.ls[i] 53 | else: 54 | self.ls_total_or[i] |= -self.ls[i] 55 | 56 | return y 57 | -------------------------------------------------------------------------------- /tools/format.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | verible-verilog-format --inplace --indentation_spaces 4 ../rtl/filter/* ../rtl/* 4 | 5 | echo "Finished!" 6 | -------------------------------------------------------------------------------- /tools/framebuffer.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | 4 | import numpy as np 5 | 6 | from color import rgb2ycbcr 7 | from debugcom import DebugCom 8 | from defaults import set_default_scalers 9 | 10 | 11 | def transfer_picture(debugcom: DebugCom, testpic, rgb_mode, padbyte): 12 | print("Conversion and Rearrangement...") 13 | start = time.time() 14 | testpic = testpic.reshape(testpic.shape[0] * testpic.shape[1], 3) 15 | rawdata = [] 16 | 17 | if rgb_mode: 18 | for pixel in testpic: 19 | if padbyte: 20 | raw = [0, pixel[2], pixel[1], pixel[0]] 21 | else: 22 | raw = [pixel[2], pixel[1], pixel[0]] 23 | 24 | rawdata.extend(raw) 25 | else: 26 | for pixel in testpic: 27 | y_raw, u_raw, v_raw = rgb2ycbcr(pixel[2], pixel[1], pixel[0]) 28 | 29 | # Limit to range of two complements 30 | if u_raw == 128: 31 | u_raw = 127 32 | if v_raw == 128: 33 | v_raw = 127 34 | 35 | assert y_raw <= 255 36 | assert -127 <= u_raw <= 127, f"u_raw {u_raw}" 37 | assert -127 <= v_raw <= 127, f"v_raw {v_raw}" 38 | 39 | if padbyte: 40 | raw = [0, y_raw, u_raw & 0xff, v_raw & 0xff] 41 | else: 42 | raw = [y_raw, u_raw & 0xff, v_raw & 0xff] 43 | 44 | rawdata.extend(raw) 45 | 46 | end = time.time() 47 | print(f"Took {end - start} seconds") 48 | 49 | debugcom.memwrite_s8(11, 0) 50 | 51 | # 256 byte is the maximum number of bytes we can currently transfer in one block transfer. 52 | # Always transfer full blocks to ensure that every transferred block is also burst written 53 | chunksize = 256 54 | number_of_chunks = math.ceil(len(rawdata) / chunksize) 55 | expected_number_of_bytes = number_of_chunks * chunksize 56 | # Fill the last chunk 57 | rawdata += [0] * (expected_number_of_bytes - len(rawdata)) 58 | 59 | print(f"Transferring...") 60 | start = time.time() 61 | rawdata_splitted = np.array_split(rawdata, number_of_chunks) 62 | for chunk in rawdata_splitted: 63 | assert (len(chunk) == 256), "Something went wrong! Chunksize not full block!" 64 | debugcom.blockmemwrite_u8(10, chunk) 65 | end = time.time() 66 | 67 | print(f"Took {end - start} seconds") 68 | 69 | 70 | def framebuffer_easy_conf(debugcom, videonorm, interlacing_mode, rgb_mode, width, bits_per_pixel, overscan=0): 71 | debugcom.configure_video_standard(videonorm) 72 | set_default_scalers(debugcom) 73 | 74 | if videonorm == "NTSC": 75 | lines_per_field = 200 + overscan # for 60 Hz (NTSC) 76 | else: 77 | lines_per_field = 256 + overscan # for 50 Hz (PAL and SECAM) 78 | 79 | height = debugcom.configure_framebuffer(width, lines_per_field, interlacing_mode, bits_per_pixel) 80 | 81 | debugcom.enable_interlacing(interlacing_mode) 82 | debugcom.enable_rgb_mode(rgb_mode) 83 | 84 | return height 85 | -------------------------------------------------------------------------------- /tools/getch.py: -------------------------------------------------------------------------------- 1 | class _Getch: 2 | """Gets a single character from standard input. Does not echo to the 3 | screen.""" 4 | 5 | def __init__(self): 6 | try: 7 | self.impl = _GetchWindows() 8 | except ImportError: 9 | self.impl = _GetchUnix() 10 | 11 | def __call__(self): 12 | return self.impl() 13 | 14 | 15 | class _GetchUnix: 16 | def __init__(self): 17 | pass 18 | 19 | def __call__(self): 20 | import sys, tty, termios 21 | fd = sys.stdin.fileno() 22 | old_settings = termios.tcgetattr(fd) 23 | try: 24 | tty.setraw(sys.stdin.fileno()) 25 | ch = sys.stdin.read(1) 26 | finally: 27 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 28 | return ch 29 | 30 | 31 | class _GetchWindows: 32 | def __init__(self): 33 | import msvcrt 34 | pass 35 | 36 | def __call__(self): 37 | import msvcrt 38 | return msvcrt.getch() 39 | 40 | 41 | getch = _Getch() 42 | -------------------------------------------------------------------------------- /tools/lint.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # Ignore *.v files as this is only uart_tx.v and uart_rx.v 4 | # which is not written by me 5 | verible-verilog-lint --rules_config_search ../rtl/*.sv ../rtl/*.svh 6 | 7 | echo "Finished!" 8 | -------------------------------------------------------------------------------- /tools/v4l.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | video_device = "/dev/v4l/by-id/usb-fushicai_usbtv007_300000000002-video-index0" 4 | 5 | 6 | def capture_still_frame(): 7 | cap = cv2.VideoCapture(video_device) 8 | w = cap.get(cv2.CAP_PROP_FRAME_WIDTH) 9 | h = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) 10 | print(w, h) 11 | 12 | # Check if camera was opened correctly 13 | if not (cap.isOpened()): 14 | print("Could not open video device") 15 | 16 | # For some reason the brightness can only be set after reading one frame 17 | ret, frame = cap.read() 18 | # cap.set(cv2.CAP_PROP_BRIGHTNESS, 350) 19 | 20 | for i in range(10): 21 | ret, frame = cap.read() 22 | 23 | cap.release() 24 | return frame 25 | 26 | 27 | def capture_video(): 28 | cap = cv2.VideoCapture(video_device) 29 | 30 | if not (cap.isOpened()): 31 | print("Could not open video device") 32 | 33 | # For some reason the brightness can only be set after reading one frame 34 | ret, frame = cap.read() 35 | # cap.set(cv2.CAP_PROP_BRIGHTNESS, 420) 36 | 37 | while (True): 38 | ret, frame = cap.read() 39 | cv2.imshow("preview", frame) 40 | # Waits for a user input to quit the application 41 | if cv2.waitKey(1) & 0xFF == ord('q'): 42 | break 43 | cap.release() 44 | return frame 45 | -------------------------------------------------------------------------------- /tools/vlc_ntsc.sh: -------------------------------------------------------------------------------- 1 | vlc v4l2:///dev/v4l/by-id/usb-fushicai_usbtv007_300000000002-video-index0:standard=ntsc 2 | -------------------------------------------------------------------------------- /tools/vlc_pal.sh: -------------------------------------------------------------------------------- 1 | vlc v4l2:///dev/v4l/by-id/usb-fushicai_usbtv007_300000000002-video-index0:standard=pal 2 | -------------------------------------------------------------------------------- /tools/vlc_secam.sh: -------------------------------------------------------------------------------- 1 | vlc v4l2:///dev/v4l/by-id/usb-fushicai_usbtv007_300000000002-video-index0:standard=secam:live-caching=0 2 | --------------------------------------------------------------------------------