├── requirements.txt ├── Manifest.py ├── src ├── decoders │ ├── Manifest.py │ ├── raw8.sv │ ├── yuv422_8bit.sv │ ├── rgb565.sv │ └── rgb888.sv ├── Manifest.py ├── d_phy_receiver.sv └── camera.sv ├── test ├── Manifest.py ├── d_phy_receiver_tb.sv └── camera_tb.sv ├── sim ├── camera_tb │ └── Manifest.py ├── d_phy_receiver_tb │ └── Manifest.py └── vsim.do ├── .gitignore ├── LICENSE-APACHE ├── .travis.yml ├── LICENSE-MIT └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | hdlmake==3.3 2 | -------------------------------------------------------------------------------- /Manifest.py: -------------------------------------------------------------------------------- 1 | modules = { 2 | "local": "./src/" 3 | } 4 | -------------------------------------------------------------------------------- /src/decoders/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | 'raw8.sv', 3 | 'rgb565.sv', 4 | 'rgb888.sv', 5 | 'yuv422_8bit.sv' 6 | ] 7 | -------------------------------------------------------------------------------- /src/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "camera.sv", 3 | "d_phy_receiver.sv" 4 | ] 5 | 6 | modules = { 7 | "local": "./decoders/" 8 | } 9 | -------------------------------------------------------------------------------- /test/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "camera_tb.sv", 3 | "d_phy_receiver_tb.sv" 4 | ] 5 | 6 | modules = { 7 | "local" : [ "../src/" ], 8 | } 9 | -------------------------------------------------------------------------------- /sim/camera_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "camera_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c camera_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/d_phy_receiver_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "d_phy_receiver_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c d_phy_receiver_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/vsim.do: -------------------------------------------------------------------------------- 1 | onfinish stop 2 | run -all 3 | if { [runStatus -full] == "break simulation_stop {\$finish}" } { 4 | echo Build succeeded 5 | quit -f -code 0 6 | } else { 7 | echo Build failed with status [runStatus -full] 8 | quit -f -code 1 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/decoders/raw8.sv: -------------------------------------------------------------------------------- 1 | module raw8 ( 2 | input logic [7:0] image_data [3:0], 3 | input logic image_data_enable, 4 | output logic [7:0] raw [3:0], 5 | output logic raw_enable 6 | ); 7 | assign raw = image_data; 8 | assign raw_enable = image_data_enable; 9 | 10 | endmodule 11 | -------------------------------------------------------------------------------- /src/decoders/yuv422_8bit.sv: -------------------------------------------------------------------------------- 1 | module yuv422_8bit ( 2 | input logic [7:0] image_data [3:0], 3 | input logic image_data_enable, 4 | output logic [23:0] yuv [1:0], 5 | output logic yuv_enable 6 | ); 7 | assign yuv[0] = {image_data[2], image_data[3], image_data[1]}; 8 | assign yuv[1] = {image_data[0], image_data[3], image_data[1]}; 9 | assign yuv_enable = image_data_enable; 10 | 11 | endmodule 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .venv 3 | *.pin 4 | *.pof 5 | *.sof 6 | *.qpf 7 | *.qsf 8 | *.sid 9 | *.map.* 10 | *.sta.* 11 | *.fit.* 12 | bitstream.tcl 13 | bitstream 14 | incremental_db 15 | *.hdb 16 | *.cdb 17 | *.ddb 18 | *.idb 19 | *.rdb 20 | *.logdb 21 | *.qmsg 22 | *.hsd 23 | *.ammdb 24 | db 25 | *.rpt 26 | *.sld 27 | *.jdi 28 | *.done 29 | *.pow.* 30 | Makefile 31 | project 32 | project.tcl 33 | files.tcl 34 | *.vcd 35 | *.vvp 36 | run.command 37 | modelsim.ini 38 | transcript 39 | work/ 40 | *.wlf 41 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Sameer Puri 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/decoders/rgb565.sv: -------------------------------------------------------------------------------- 1 | module rgb565 ( 2 | input logic [7:0] image_data [3:0], 3 | input logic image_data_enable, 4 | output logic [15:0] rgb [1:0], 5 | output logic rgb_enable 6 | ); 7 | 8 | logic [31:0] unpacked_image_data; 9 | assign unpacked_image_data = {image_data[3], image_data[2], image_data[1], image_data[0]}; 10 | 11 | assign rgb[0] = {unpacked_image_data[15:11], unpacked_image_data[10:5], unpacked_image_data[4:0]}; 12 | assign rgb[1] = {unpacked_image_data[31:27], unpacked_image_data[26:21], unpacked_image_data[20:16]}; 13 | assign rgb_enable = image_data_enable; 14 | 15 | endmodule 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: python 3 | os: linux 4 | addons: 5 | apt: 6 | update: false 7 | packages: 8 | - lib32z1 9 | - lib32stdc++6 10 | - libexpat1:i386 11 | - libc6:i386 12 | - libsm6:i386 13 | - libncurses5:i386 14 | - libx11-6:i386 15 | - zlib1g:i386 16 | - libxext6:i386 17 | - libxft2:i386 18 | 19 | install: 20 | - pip install -r requirements.txt 21 | - stat /home/travis/intelFPGA/19.1/modelsim_ase || (curl 'http://download.altera.com/akdlm/software/acdsinst/19.1std/670/ib_installers/ModelSimSetup-19.1.0.670-linux.run' -o ModelSimSetup.run && chmod +x ModelSimSetup.run && travis_wait 30 ./ModelSimSetup.run --mode unattended --accept_eula 1 && sed -i 's/linux_rh60/linux/g' /home/travis/intelFPGA/19.1/modelsim_ase/vco ) 22 | script: 23 | - export PATH=$PATH:/home/travis/intelFPGA/19.1/modelsim_ase/bin 24 | - cd ./sim/d_phy_receiver_tb/ && hdlmake fetch && hdlmake && make 25 | - cd - 26 | - cd ./sim/camera_tb/ && hdlmake fetch && hdlmake && make 27 | 28 | cache: 29 | directories: 30 | - /home/travis/intelFPGA/ 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sameer Puri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/decoders/rgb888.sv: -------------------------------------------------------------------------------- 1 | module rgb888 ( 2 | input logic clock_p, 3 | input logic clock_n, 4 | input logic [7:0] image_data [3:0], 5 | input logic image_data_enable, 6 | output logic [23:0] rgb [1:0], 7 | output logic [1:0] rgb_enable 8 | ); 9 | 10 | // Fifo Memory Order 11 | // 0 = BGRB -> BRGB 12 | // 1 = GRBG -> GBRG 13 | // 2 = RBGR -> RGBR 14 | logic [1:0] state = 2'd0; 15 | 16 | logic [7:0] last_upper_image_data [1:0] = '{8'd0, 8'd0}; 17 | 18 | assign rgb_enable[0] = image_data_enable; 19 | assign rgb_enable[1] = image_data_enable && state == 2'd2; 20 | 21 | assign rgb[0] = state == 2'd0 ? {image_data[2], image_data[1], image_data[0]} 22 | : state == 2'd1 ? {image_data[1], image_data[0], last_upper_image_data[3]} 23 | : state == 2'd2 ? {image_data[0], last_upper_image_data[3], last_upper_image_data[2]}; 24 | 25 | assign rgb[1] = {image_data[3], image_data[2], image_data[1]}; 26 | 27 | always @(posedge clock_p or posedge clock_n) 28 | begin 29 | if (image_data_enable) 30 | begin 31 | state <= state == 2'd2 ? 2'd0 : state + 2'd1; 32 | last_upper_image_data <= image_data[3:2]; 33 | end 34 | end 35 | 36 | endmodule 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIPI CSI 2 Receiver 2 | 3 | [![Build Status](https://travis-ci.com/hdl-util/mipi-csi-2.svg?branch=master)](https://travis-ci.com/hdl-util/mipi-csi-2) 4 | 5 | ## To-do List 6 | * Primary format decoding 7 | * [x] RGB888 8 | * [x] RGB565 9 | * [x] YUV422 8-bit 10 | * [x] RAW8 11 | * [ ] RAW10 12 | * Tests 13 | * [x] D-PHY 14 | * [x] CSI-2 15 | * [ ] Decoding 16 | * Error-checking and correction 17 | * [ ] Header ECC 18 | * [ ] Footer Checksum 19 | * N-lane 20 | * [x] 1 lane 21 | * [x] 2 lane 22 | * [ ] 3 lane 23 | * Roadblock: will receive more bytes than the 32-bit buffer 24 | * Consider long packet with 8 bytes 25 | * First 3 from header go from corresponding lanes 26 | * Header byte 4 comes from lane 1, Data byte 1, 2 come from lanes 2 & 3 27 | * Data byte 3, 4, 5 (!) come from lanes 1, 2, & 3 28 | * Thus, you are stuck with extra, on the same clock the user gets the buffer 29 | * [x] 4 lane 30 | 31 | ## Reference Documents 32 | 33 | These documents are not hosted here! They are available on Library Genesis and at other locations. 34 | 35 | * [MIPI CSI-2 Specification](https://b-ok.cc/book/5370801/fbaeb9) 36 | * [MIPI D-PHY Specification](https://b-ok.cc/book/5370804/7f174a) 37 | 38 | ## Special Thanks 39 | 40 | * [Gaurav Singh's posts on his IMX219 CSI-2 RX implementation](https://www.circuitvalley.com/2020/02/imx219-camera-mipi-csi-receiver-fpga-lattice-raspberry-pi-camera.html) 41 | -------------------------------------------------------------------------------- /test/d_phy_receiver_tb.sv: -------------------------------------------------------------------------------- 1 | module d_phy_receiver_tb(); 2 | 3 | // Initially, lines are grounded 4 | logic clock_p = 0; 5 | logic clock_n = 0; 6 | always 7 | begin 8 | #2ns; 9 | clock_n <= ~clock_n; 10 | clock_p <= clock_n; 11 | end 12 | 13 | logic data_p = 0; 14 | logic data_n; 15 | assign data_n = ~data_p; 16 | 17 | logic reset = 0; 18 | logic [7:0] data; 19 | logic enable; 20 | 21 | d_phy_receiver #(.ZERO_ACCUMULATOR_WIDTH(2)) d_phy_receiver ( 22 | .clock_p(clock_p), 23 | .data_p(data_p), 24 | // Synchronous reset 25 | // D-PHY clocks for TCLK-POST after the HS RX ends so this will stick 26 | .reset(reset), 27 | // Output byte 28 | .data(data), 29 | // Whether the output byte is valid 30 | .enable(enable) 31 | ); 32 | 33 | logic [7:0] shift_out = 8'd0; 34 | logic [2:0] shift_index = 3'd0; 35 | always @(posedge clock_p or posedge clock_n) 36 | begin 37 | data_p <= shift_out[shift_index]; // Recall: LSB first 38 | shift_index <= shift_index + 1'd1; 39 | end 40 | 41 | localparam TEST1_LEN = 128; 42 | logic [7:0] TEST1 [0:TEST1_LEN-1] = '{8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF, 8'hFE, 8'hED, 8'hFA, 8'hCE, 8'hCA, 8'hFE, 8'hBE, 8'hEF}; 43 | 44 | integer i = -2; 45 | always @(posedge clock_p) 46 | begin 47 | if (shift_index == 3'd7 && i < TEST1_LEN) 48 | begin 49 | if (i == -2) 50 | shift_out <= 8'd0; 51 | else if (i == -1) 52 | shift_out <= 8'b10111000; 53 | else 54 | shift_out <= TEST1[i]; 55 | i <= i + 1; 56 | end 57 | if (enable && i > 0 && i <= TEST1_LEN + 1) 58 | begin 59 | assert(data == TEST1[i - 2]) else $fatal(1, "data not expected: %h vs %h", data, TEST1[i - 2]); 60 | if (i >= TEST1_LEN) 61 | i <= i + 1; 62 | end 63 | else if (i > TEST1_LEN + 1) 64 | begin 65 | assert(!enable) else $fatal(1, "unexpected enables after TX finished"); 66 | if (i == TEST1_LEN + 2) 67 | reset <= 1'd1; 68 | else if (i == TEST1_LEN + 3) 69 | reset <= 1'd0; 70 | else 71 | begin 72 | assert(d_phy_receiver.state == 2'd0) else $fatal(1, "receiver is in %d but should've returned to UNKNOWN", d_phy_receiver.state); 73 | $finish; 74 | end 75 | i <= i + 1; 76 | end 77 | 78 | 79 | end 80 | 81 | endmodule 82 | -------------------------------------------------------------------------------- /src/d_phy_receiver.sv: -------------------------------------------------------------------------------- 1 | // D-PHY (CIL-SFNN) receiver that can only do HS RX. 2 | // Why? Some FPGAs have MIPI lines connected to a single port. 3 | // This means you can only pick one IO standard: HSTL 1.2V for LP RX or LVDS for HS RX. 4 | // LVDS is picked, and though this doesn't technically comply with D-PHY, it should work. 5 | // This means the protocol layer should send a reset after receiving the last byte. 6 | // https://www.intel.com/content/www/us/en/programmable/documentation/mcn1446711751001.html#mcn1448380606073 7 | module d_phy_receiver #( 8 | // Gives the receiver resistance to noise by expecting 0s before a sync sequence 9 | parameter int ZERO_ACCUMULATOR_WIDTH = 3 10 | ) ( 11 | input logic clock_p, 12 | input logic data_p, 13 | // Synchronous reset 14 | // D-PHY clocks for TCLK-POST after the HS RX ends so this will stick 15 | input logic reset, 16 | // Output byte 17 | output logic [7:0] data, 18 | // Whether the output byte is valid 19 | output logic enable 20 | ); 21 | 22 | logic dataout_h = 1'd0, dataout_l = 1'd0; 23 | 24 | always_ff @(posedge clock_p) dataout_h <= data_p; 25 | always_ff @(negedge clock_p) dataout_l <= data_p; 26 | 27 | logic [8:0] internal_data = 9'd0; 28 | 29 | always_ff @(posedge clock_p) 30 | internal_data <= {dataout_l, dataout_h, internal_data[8:2]}; // "Each byte shall be transmitted least significant bit first." 31 | 32 | localparam bit [1:0] STATE_UNKNOWN = 2'd0; 33 | localparam bit [1:0] STATE_SYNC_IN_PHASE = 2'd1; 34 | localparam bit [1:0] STATE_SYNC_OUT_OF_PHASE = 2'd2; 35 | logic [1:0] state = STATE_UNKNOWN; 36 | 37 | assign data = state == STATE_SYNC_IN_PHASE ? internal_data[7:0] : state == STATE_SYNC_OUT_OF_PHASE ? internal_data[8:1] : 8'dx; 38 | 39 | // Byte counter 40 | logic [1:0] counter = 2'd0; 41 | 42 | assign enable = state != STATE_UNKNOWN && counter == 2'd0; 43 | 44 | logic [ZERO_ACCUMULATOR_WIDTH-1:0] zero_accumulator = ZERO_ACCUMULATOR_WIDTH'(0); 45 | always_ff @(posedge clock_p) 46 | begin 47 | if (internal_data[1] || internal_data[0]) 48 | zero_accumulator <= ZERO_ACCUMULATOR_WIDTH'(0); 49 | else if (zero_accumulator + 1'd1 == ZERO_ACCUMULATOR_WIDTH'(0)) 50 | zero_accumulator <= zero_accumulator; 51 | else 52 | zero_accumulator <= zero_accumulator + 1'd1; 53 | end 54 | 55 | always_ff @(posedge clock_p) 56 | begin 57 | if (reset) 58 | begin 59 | state <= STATE_UNKNOWN; 60 | counter <= 2'dx; 61 | end 62 | else 63 | begin 64 | if (state == STATE_UNKNOWN) 65 | begin 66 | if (internal_data == 9'b101110000 && zero_accumulator + 1'd1 == ZERO_ACCUMULATOR_WIDTH'(0)) 67 | begin 68 | state <= STATE_SYNC_OUT_OF_PHASE; 69 | counter <= 2'd3; 70 | end 71 | else if (internal_data[7:0] == 8'b10111000 && zero_accumulator + 1'd1 == ZERO_ACCUMULATOR_WIDTH'(0)) 72 | begin 73 | state <= STATE_SYNC_IN_PHASE; 74 | counter <= 2'd3; 75 | end 76 | else 77 | begin 78 | state <= STATE_UNKNOWN; 79 | counter <= 2'dx; 80 | end 81 | end 82 | else 83 | begin 84 | state <= state; 85 | counter <= counter - 2'd1; 86 | `ifdef MODEL_TECH 87 | if (counter == 2'd0) 88 | $display("%h", data); 89 | `endif 90 | end 91 | end 92 | end 93 | 94 | endmodule 95 | -------------------------------------------------------------------------------- /test/camera_tb.sv: -------------------------------------------------------------------------------- 1 | module camera_tb(); 2 | 3 | // Initially, lines are grounded 4 | logic clock_p = 0; 5 | logic clock_n = 0; 6 | always 7 | begin 8 | #2ns; 9 | clock_n <= ~clock_n; 10 | clock_p <= clock_n; 11 | end 12 | 13 | logic [1:0] data_p = 2'd0; 14 | logic [1:0] data_n; 15 | assign data_n = ~data_p; 16 | 17 | logic [1:0] virtual_channel; 18 | logic [15:0] word_count; 19 | logic [7:0] image_data [3:0]; 20 | logic [5:0] image_data_type; 21 | logic image_data_enable, interrupt; 22 | 23 | camera #(.NUM_LANES(2), .ZERO_ACCUMULATOR_WIDTH(2)) camera ( 24 | .clock_p(clock_p), 25 | .data_p(data_p), 26 | // Corresponding virtual channel for the image data 27 | .virtual_channel(virtual_channel), 28 | // Total number of words in the current packet 29 | .word_count(word_count), 30 | // See Section 12 for how this should be parsed 31 | .image_data(image_data), 32 | .image_data_type(image_data_type), 33 | // Whether there is output data ready 34 | .image_data_enable(image_data_enable), 35 | .interrupt(interrupt) 36 | ); 37 | 38 | logic [7:0] shift_out [1:0] = '{ 8'd0, 8'd0 }; 39 | logic [2:0] shift_index = 3'd0; 40 | always_ff @(posedge clock_p or posedge clock_n) 41 | begin 42 | data_p[0] <= shift_out[0][shift_index]; 43 | data_p[1] <= shift_out[1][shift_index]; 44 | shift_index <= shift_index + 1'd1; 45 | end 46 | 47 | // Short Packet (Virtual Channel: 0, Data Type: 0x08 (Generic Short Packet), Word Count: 0xFACE, ECC: 0x12) 48 | logic [7:0] TEST1 [0:5] = '{8'b10111000, 8'b10111000, 8'h08, 8'hCE, 8'hFA, 8'h12}; 49 | // Long Packet (Virtual Channel: 0, Data Type: 0x18 (YUV Data), Word Count: 8, ECC: 0xFE, Data: 0x0D15EA5EFEE1DEAD CRC: 0xF00D) 50 | logic [7:0] TEST2 [0:15] = '{8'b10111000, 8'b10111000, 8'h18, 8'd8, 8'd0, 8'hFE, 8'hAD, 8'hDE, 8'hE1, 8'hFE, 8'h5E, 8'hEA, 8'h15, 8'h0D, 8'hD0, 8'hF0}; 51 | 52 | integer current_test = 0; 53 | logic [7:0] test_index = 1'd0; 54 | integer image_data_index = 0; 55 | logic [31:0] expected_image_data; 56 | assign expected_image_data = {TEST2[image_data_index + 9], TEST2[image_data_index + 8], TEST2[image_data_index + 7], TEST2[image_data_index + 6]}; 57 | logic [31:0] actual_image_data; 58 | assign actual_image_data = {image_data[3], image_data[2], image_data[1], image_data[0]}; 59 | 60 | always_ff @(posedge clock_p) 61 | begin 62 | case(current_test) 63 | 0: begin 64 | if (interrupt) 65 | begin 66 | assert (virtual_channel == 2'd0) else $fatal(1, "Virtual channel should be 0"); 67 | assert (camera.header_ecc == TEST1[5]) else $fatal(1, "Header ecc incorrect"); 68 | assert (word_count == {TEST1[4], TEST1[3]}) else $fatal(1, "Expected word count '%h%h' but was %h", TEST1[4], TEST1[3], word_count); 69 | assert (image_data_type == TEST1[2]) else $fatal(1, "Expected data type %h but was %h", TEST1[2], image_data_type); 70 | $display("Test 1 complete"); 71 | current_test <= 1; 72 | test_index <= 1'd0; 73 | shift_out <= '{8'd0, 8'd0}; // Required to satisfy zero counter expectations in dphy 74 | end 75 | 76 | if (shift_index == 3'd7) 77 | begin 78 | shift_out <= '{TEST1[test_index + 1'd1], TEST1[test_index]}; 79 | test_index <= test_index + 2'd2; 80 | end 81 | end 82 | 1: begin 83 | if (interrupt) 84 | begin 85 | assert (virtual_channel == 2'd0) else $fatal(1, "Virtual channel should be 0"); 86 | assert (camera.header_ecc == TEST2[5]) else $fatal(1, "Header ecc incorrect"); 87 | assert (word_count == {TEST2[4], TEST2[3]}) else $fatal(1, "Expected word count '%h%h' but was %h", TEST2[4], TEST2[3], word_count); 88 | assert (image_data_type == TEST2[2]) else $fatal(1, "Expected data type %h but was %h", TEST2[2], image_data_type); 89 | if (image_data_enable) 90 | begin 91 | assert (actual_image_data == expected_image_data) else $fatal(1, "Expected image data to be %h but was %h", expected_image_data, actual_image_data); 92 | image_data_index = image_data_index + 4; 93 | if (image_data_index == 8) 94 | begin 95 | $display("Test 2 complete"); 96 | $finish; 97 | end 98 | end 99 | end 100 | 101 | if (test_index < 8'd16 && shift_index == 3'd7) 102 | begin 103 | shift_out <= '{TEST2[test_index + 1'd1], TEST2[test_index]}; 104 | test_index <= test_index + 2'd2; 105 | end 106 | end 107 | endcase 108 | end 109 | 110 | logic last_image_data_enable = 1'b0, last_interrupt = 1'b0; 111 | 112 | always_ff @(posedge clock_p) 113 | begin 114 | last_image_data_enable <= image_data_enable; 115 | last_interrupt <= interrupt; 116 | assert (!(last_image_data_enable && image_data_enable)) else $fatal(1, "double image data enable occurred"); 117 | assert (!(last_interrupt && interrupt)) else $fatal(1, "double interrupt occurred"); 118 | end 119 | 120 | endmodule 121 | -------------------------------------------------------------------------------- /src/camera.sv: -------------------------------------------------------------------------------- 1 | module camera #( 2 | parameter int NUM_LANES = 2, 3 | // Gives the underlying d_phy_receivers resistance to noise by expecting 0s before a sync sequence 4 | parameter int ZERO_ACCUMULATOR_WIDTH = 3 5 | ) ( 6 | input logic clock_p, 7 | input logic [NUM_LANES-1:0] data_p, 8 | // Corresponding virtual channel for the image data 9 | output logic [1:0] virtual_channel, 10 | // Total number of words in the current packet 11 | output logic [15:0] word_count, 12 | 13 | output logic interrupt, 14 | 15 | // See Section 12 for how this should be parsed 16 | output logic [7:0] image_data [3:0] = '{8'd0, 8'd0, 8'd0, 8'd0}, 17 | output logic [5:0] image_data_type, 18 | // Whether there is output data ready 19 | output logic image_data_enable, 20 | 21 | output logic frame_start, 22 | output logic frame_end, 23 | output logic line_start, 24 | output logic line_end, 25 | 26 | // The intention of the Generic Short Packet Data Types is to provide a mechanism for including timing 27 | // information for the opening/closing of shutters, triggering of flashes, etc within the data stream. 28 | output logic generic_short_data_enable, 29 | output logic [15:0] generic_short_data 30 | ); 31 | 32 | logic [NUM_LANES-1:0] reset = NUM_LANES'(0); 33 | 34 | 35 | logic [7:0] data [NUM_LANES-1:0]; 36 | logic [NUM_LANES-1:0] enable; 37 | 38 | genvar i; 39 | generate 40 | for (i = 0; i < NUM_LANES; i++) 41 | begin: lane_receivers 42 | d_phy_receiver #(.ZERO_ACCUMULATOR_WIDTH(ZERO_ACCUMULATOR_WIDTH)) d_phy_receiver ( 43 | .clock_p(clock_p), 44 | .data_p(data_p[i]), 45 | .reset(reset[i]), 46 | .data(data[i]), 47 | .enable(enable[i]) 48 | ); 49 | end 50 | endgenerate 51 | 52 | logic [7:0] packet_header [3:0] = '{8'd0, 8'd0, 8'd0, 8'd0}; 53 | assign virtual_channel = packet_header[0][7:6]; 54 | logic [5:0] data_type; 55 | assign data_type = packet_header[0][5:0]; 56 | assign image_data_type = data_type; 57 | 58 | assign frame_start = data_type == 6'd0; 59 | assign frame_end = data_type == 6'd1; 60 | assign line_start = data_type == 6'd2; 61 | assign line_end = data_type == 6'd3; 62 | assign generic_short_data_enable = data_type >= 6'd8 && data_type <= 6'hF && reset[0]; 63 | assign generic_short_data = word_count; 64 | 65 | assign word_count = {packet_header[2], packet_header[1]}; // Recall: LSB first 66 | logic [7:0] header_ecc; 67 | assign header_ecc = packet_header[3]; 68 | 69 | logic [2:0] header_index = 3'd0; 70 | logic [16:0] word_counter = 17'd0; 71 | logic [1:0] data_index = 2'd0; 72 | 73 | logic already_triggered = 1'd0; 74 | // Count off multiples of four 75 | // Shouldn't be the first byte 76 | assign image_data_enable = data_type >= 6'h18 && data_type <= 6'h2F && data_index == 2'd0 && word_counter != 17'd0 && word_counter <= word_count && !already_triggered; 77 | always_ff @(posedge clock_p) 78 | already_triggered = already_triggered ? enable == NUM_LANES'(0) : image_data_enable; 79 | 80 | assign interrupt = image_data_enable || (reset[0] && data_type <= 6'hF); 81 | 82 | integer j; 83 | always_ff @(posedge clock_p) 84 | begin 85 | // Lane reception 86 | for (j = 0; j < NUM_LANES; j++) 87 | begin 88 | if (enable[j]) // Receive byte 89 | begin 90 | `ifdef MODEL_TECH 91 | $display("Receiving on lane %d", 3'(j + 1)); 92 | `endif 93 | if (header_index < 3'd4) // Packet header 94 | begin 95 | packet_header[header_index] <= data[j]; 96 | header_index = header_index + 1'd1; 97 | end 98 | else // Long packet receive 99 | begin 100 | // Image data (YUV, RGB, RAW) 101 | if (data_type >= 6'h18 && data_type <= 6'h2F && word_counter < word_count) 102 | begin 103 | image_data[data_index] <= data[j]; 104 | data_index = data_index + 2'd1; // Wrap-around 4 byte counter 105 | end 106 | // Footer 107 | else 108 | begin 109 | end 110 | word_counter = word_counter + 17'd1; 111 | end 112 | end 113 | end 114 | 115 | // Lane resetting 116 | for (j = 0; j < NUM_LANES; j++) 117 | begin 118 | if (enable != NUM_LANES'(0)) 119 | begin 120 | if (data_type <= 6'h0F && header_index + 3'(j) >= 3'd4 && !reset[j]) // Reset on short packet end 121 | begin 122 | `ifdef MODEL_TECH 123 | $display("Resetting lane %d", 3'(j + 1)); 124 | `endif 125 | reset[j] <= 1'b1; 126 | end 127 | else if (header_index + 3'(j) >= 3'd4 && header_index + word_counter + 17'(j) >= 17'(word_count) + 17'd2 + 3'd4 && !reset[j]) // Reset on long packet end 128 | begin 129 | `ifdef MODEL_TECH 130 | $display("Resetting lane %d", 3'(j + 1)); 131 | `endif 132 | reset[j] <= 1'b1; 133 | end 134 | end 135 | end 136 | // Synchronous state reset (next clock) 137 | // The remaining lanes are in a sticky reset state where they remain reset until the first lane also resets 138 | if (reset[0]) // Know the entire state is gone for sure if the first lane resets 139 | begin 140 | header_index = 3'd0; 141 | word_counter = 17'd0; 142 | data_index = 2'd0; 143 | reset <= NUM_LANES'(0); 144 | end 145 | end 146 | 147 | endmodule 148 | --------------------------------------------------------------------------------