├── .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 | FPGA
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 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tools/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------