diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..656387a --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +.vscode +.venv +*.pin +*.pof +*.sof +*.qpf +*.qsf +*.sid +*.map.* +*.sta.* +*.fit.* +bitstream.tcl +bitstream +incremental_db +*.hdb +*.cdb +*.ddb +*.idb +*.rdb +*.logdb +*.qmsg +*.hsd +*.ammdb +db +*.rpt +*.sld +*.jdi +*.done +*.pow.* +Makefile +project +project.tcl +files.tcl +*.vcd +*.vvp +run.command +modelsim.ini +transcript +work/ +*.wlf +ip_cores/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4be66bd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +sudo: required +language: python +os: linux +addons: + apt: + update: false + packages: + - lib32z1 + - lib32stdc++6 + - libexpat1:i386 + - libc6:i386 + - libsm6:i386 + - libncurses5:i386 + - libx11-6:i386 + - zlib1g:i386 + - libxext6:i386 + - libxft2:i386 + +install: + - pip install -r requirements.txt + - 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 ) +script: + - export PATH=$PATH:/home/travis/intelFPGA/19.1/modelsim_ase/bin + - cd ./sim/imx219_tb/ && hdlmake fetch && hdlmake && make + - cd - + +cache: + directories: + - /home/travis/intelFPGA/ diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..204a590 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2020 Sameer Puri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..52a552a --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Sameer Puri + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Manifest.py b/Manifest.py new file mode 100644 index 0000000..8f4d9e9 --- /dev/null +++ b/Manifest.py @@ -0,0 +1,3 @@ +modules = { + "local": "./src/" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..1be0190 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# MIPI CCS v1.1 + +[![Build Status](https://travis-ci.com/hdl-util/mipi-ccs.svg?branch=master)](https://travis-ci.com/hdl-util/mipi-ccs) + +## To-do List + +* [ ] Power-off Mode +* [ ] Software reset + * [ ] Fast standby control +* [ ] Mode select +* [ ] Set virtual channel +* [ ] Streaming mode +* Camera control + * [ ] Analog/Digital gain + * [ ] Exposure + * [ ] Orientation + +* Spinlock watching i2c until ready + * [ ] reset_max_delay + * [ ] reset_min_time + +## Reference Documents + +These documents are not hosted here! They are available on Library Genesis and at other locations. + +* [MIPI CCS v1.1](https://b-ok.cc/book/5437872/02a689) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..38ef5c1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +hdlmake==3.3 diff --git a/sim/imx219_tb/Manifest.py b/sim/imx219_tb/Manifest.py new file mode 100644 index 0000000..d115aa9 --- /dev/null +++ b/sim/imx219_tb/Manifest.py @@ -0,0 +1,9 @@ +action = "simulation" +sim_tool = "modelsim" +sim_top = "imx219_tb" + +sim_post_cmd = "vsim -novopt -do ../vsim.do -c imx219_tb" + +modules = { + "local" : [ "../../test/" ], +} diff --git a/sim/vsim.do b/sim/vsim.do new file mode 100644 index 0000000..4fd11e5 --- /dev/null +++ b/sim/vsim.do @@ -0,0 +1 @@ +run -all; diff --git a/src/Manifest.py b/src/Manifest.py new file mode 100644 index 0000000..b092a32 --- /dev/null +++ b/src/Manifest.py @@ -0,0 +1,11 @@ +files = [ + "imx219.sv" +] + + +modules = { + "git": [ + "git@github.com:hdl-util/i2c.git::master", + ] +} +fetchto = "../ip_cores" diff --git a/src/imx219.sv b/src/imx219.sv new file mode 100644 index 0000000..174bc13 --- /dev/null +++ b/src/imx219.sv @@ -0,0 +1,267 @@ +module imx219 #( + parameter INPUT_CLK_RATE = 48000000, + parameter TARGET_SCL_RATE = 400000, + // Some IMX219 modules have a different address, change this if yours does + parameter ADDRESS = 8'h20 +) ( + input logic clk_in, + inout logic scl, + inout logic sda, + // 0 = Power off + // 1 = Software standby + // 2 = Streaming + input logic [1:0] mode, + + // 0 = 3280x2464 + // 1 = 1920x1080 + // 2 = 1640x1232 + // 3 = 640x480 + input logic [1:0] resolution, + // 0 = RAW8 + // 1 = RAW10 + input logic format, + // input logic horizontal_flip, + // input logic vertical_flip, + // input logic [7:0] analog_gain, + // input logic [15:0] digital_gain, + // input logic [15:0] exposure, // aka integration time + + // Goes high when inputs match sensor state + // Changing inputs when the sensor isn't ready could put the sensor into an unexpected state + output logic ready, + output logic power_enable, + // IMX219 Model ID did not match + output logic model_err = 1'b0, + output logic nack_err = 1'b0 +); + +logic bus_clear; + +logic transfer_start = 1'b0; +logic transfer_continues = 1'b0; +logic i2c_mode = 1'b0; +logic [7:0] address; +assign address = ADDRESS + i2c_mode; +logic [7:0] data_tx = 8'd0; + +logic transfer_ready; +logic interrupt; +logic transaction_complete; +logic nack; +logic [7:0] data_rx; +logic address_err; + +i2c_master #(.INPUT_CLK_RATE(INPUT_CLK_RATE), .TARGET_SCL_RATE(TARGET_SCL_RATE)) i2c_master ( + .scl(scl), + .clk_in(clk_in), + .bus_clear(bus_clear), + .sda(sda), + .address(address), + .transfer_start(transfer_start), + .transfer_continues(transfer_continues), + .data_tx(data_tx), + .transfer_ready(transfer_ready), + .interrupt(interrupt), + .transaction_complete(transaction_complete), + .nack(nack), + .data_rx(data_rx), + .address_err(address_err) +); + +logic [15:0] MODEL_ID = 16'h0219; + +logic [24:0] PRE_STANDBY [2:0] = '{ + {1'b1, 16'h0000, MODEL_ID[15:8]}, // Read module_model_id high + {1'b1, 16'h0001, MODEL_ID[7:0]}, // Read module_model_id low + // {1'b0, 16'h0100, 8'd2}, // mode_select <= streaming (forces LP-11 on standby) + {1'b0, 16'h0100, 8'd1} // mode_select <= standby +}; + +logic [24:0] PRE_STREAM [57:0] = '{ + {1'b0, 16'h30eb, 8'h0c}, // Manufacturer address access code + {1'b0, 16'h30eb, 8'h05}, + {1'b0, 16'h300a, 8'hff}, + {1'b0, 16'h300b, 8'hff}, + {1'b0, 16'h30eb, 8'h05}, + {1'b0, 16'h30eb, 8'h09}, + {1'b0, 16'h0114, 8'h01}, // CSI Lane Count (2) + {1'b0, 16'h0128, 8'h00}, // MIPI Global timing (auto) + {1'b0, 16'h012a, 8'h18}, // External Clock Frequency MSB (24MHz) + {1'b0, 16'h012b, 8'h00}, // External Clock Frequency LSB + {1'b0, 16'h0160, resolution == 2'd0 ? 8'h0d : 8'h06}, // Frame length MSB (15 FPS for full-frame, 30 FPS for other resolutions) + {1'b0, 16'h0161, resolution == 2'd0 ? 8'hc6 : 8'he3}, // Frame length LSB + {1'b0, 16'h0162, 8'h0d}, // Pixel clocks per line MSB (3448) + {1'b0, 16'h0163, 8'h78}, // Pixel clocks per line LSB + {1'b0, 16'h0164, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'h02 : resolution == 2'd2 ? 8'h00: 8'h03}, // X-address start MSB + {1'b0, 16'h0165, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'ha8 : resolution == 2'd2 ? 8'h00: 8'he8}, // X-address start LSB + {1'b0, 16'h0166, resolution == 2'd0 ? 8'h0c : resolution == 2'd1 ? 8'h08 : resolution == 2'd2 ? 8'h0c: 8'h08}, // X-address end MSB + {1'b0, 16'h0167, resolution == 2'd0 ? 8'hcf : resolution == 2'd1 ? 8'h27 : resolution == 2'd2 ? 8'hcf: 8'he7}, // X-address end LSB + {1'b0, 16'h0168, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'h02 : resolution == 2'd2 ? 8'h00: 8'h02}, // Y-address start MSB + {1'b0, 16'h0169, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'hb4 : resolution == 2'd2 ? 8'h00: 8'hf0}, // Y-address start LSB + {1'b0, 16'h016a, resolution == 2'd0 ? 8'h09 : resolution == 2'd1 ? 8'h06 : resolution == 2'd2 ? 8'h09: 8'h06}, // Y-address end MSB + {1'b0, 16'h016b, resolution == 2'd0 ? 8'h9f : resolution == 2'd1 ? 8'heb : resolution == 2'd2 ? 8'h9f: 8'haf}, // Y-address end LSB + + {1'b0, 16'h016c, resolution == 2'd0 ? 8'h0c : resolution == 2'd1 ? 8'h07 : resolution == 2'd2 ? 8'h06 : 8'h06}, // X-output size MSB + {1'b0, 16'h016d, resolution == 2'd0 ? 8'hd0 : resolution == 2'd1 ? 8'h80 : resolution == 2'd2 ? 8'h68 : 8'h68}, // X-output size LSB + {1'b0, 16'h016e, resolution == 2'd0 ? 8'h09 : resolution == 2'd1 ? 8'h04 : resolution == 2'd2 ? 8'h04 : 8'h04}, // Y-output size MSB + {1'b0, 16'h016f, resolution == 2'd0 ? 8'ha0 : resolution == 2'd1 ? 8'h38 : resolution == 2'd2 ? 8'hd0 : 8'hd0}, // Y-output size LSB + {1'b0, 16'h0170, 8'h01}, // X odd increment + {1'b0, 16'h0171, 8'h01}, // Y odd increment + {1'b0, 16'h0174, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'h00 : resolution == 2'd2 ? 8'h01 : 8'h03}, // Vertical binning mode + {1'b0, 16'h0175, resolution == 2'd0 ? 8'h00 : resolution == 2'd1 ? 8'h00 : resolution == 2'd2 ? 8'h01 : 8'h03}, // Horizontal binning mode + {1'b0, 16'h018c, format ? 8'h0a : 8'h08}, // CSI data format MSB + {1'b0, 16'h018d, format ? 8'h0a : 8'h08}, // CSI data format LSB + {1'b0, 16'h0301, format ? 8'h05 : 8'h04}, // Video timing pixel clock divider (/5 for 10-bit, /4 for 8-bit) + {1'b0, 16'h0303, 8'h01}, // Video timing system clock divider (always /1) + {1'b0, 16'h0304, 8'h03}, // External (pre-PLL) clock divider for video timing (3 for 24MHz to 27MHz) + {1'b0, 16'h0305, 8'h03}, // External (pre-PLL) clock divider for output (3 for 24MHz to 27MHz) + {1'b0, 16'h0306, 8'h00}, // PLL video timing system multiplier MSB + {1'b0, 16'h0307, 8'h39}, // PLL video timing system multiplier LSB + {1'b0, 16'h0309, format ? 8'h0a : 8'h08}, // Output pixel clock divider (/10 for 10-bit, /8 for 8-bit) + {1'b0, 16'h030b, 8'h01}, // Output sytem clock divider (always /2) + {1'b0, 16'h030c, 8'h00}, // PLL output system clock multiplier MSB + {1'b0, 16'h030d, 8'h72}, // PLL output system clock multiplier LSB (DDR clock, as compared to 0x0307) + {1'b0, 16'h0624, resolution == 2'd0 ? 8'h0c : resolution == 2'd1 ? 8'h07 : resolution == 2'd2 ? 8'h06 : 8'h06}, // Test pattern window width MSB + {1'b0, 16'h0625, resolution == 2'd0 ? 8'hd0 : resolution == 2'd1 ? 8'h80 : resolution == 2'd2 ? 8'h68 : 8'h68}, // Test pattern window width LSB + {1'b0, 16'h0626, resolution == 2'd0 ? 8'h09 : resolution == 2'd1 ? 8'h04 : resolution == 2'd2 ? 8'h04 : 8'h04}, // Test pattern window height MSB + {1'b0, 16'h0627, resolution == 2'd0 ? 8'ha0 : resolution == 2'd1 ? 8'h38 : resolution == 2'd2 ? 8'hd0 : 8'hd0}, // Test pattern window height LSB + {1'b0, 16'h455e, 8'h00}, // CMOS Image Sensor Tuning for all below + {1'b0, 16'h471e, 8'h4b}, + {1'b0, 16'h4767, 8'h0f}, + {1'b0, 16'h4750, 8'h14}, + {1'b0, 16'h4540, 8'h00}, + {1'b0, 16'h47b4, 8'h14}, + {1'b0, 16'h4713, 8'h30}, + {1'b0, 16'h478b, 8'h10}, + {1'b0, 16'h478f, 8'h10}, + {1'b0, 16'h4793, 8'h10}, + {1'b0, 16'h4797, 8'h0e}, + {1'b0, 16'h479b, 8'h0e} +}; + +logic [24:0] POST_STREAM [0:0] = '{ + {1'b0, 16'h0x0100, 8'h00} // Send to standby + // TODO: standby spinlock +}; + + +// 0 = Off +// 1 = Pre-Standby +// 2 = Standby +// 3 = Pre-Stream +// 4 = Stream +// 5 = Modify Stream +// 6 = Post Stream (shutting down) +// 7 = Error +logic [2:0] sensor_state = 3'd0; + +logic [7:0] rom_counter = 8'd0; +logic [1:0] byte_counter = 2'd0; + +// Uninit, Standby, or Stream +assign ready = sensor_state == 3'd0 || sensor_state == 3'd2 || sensor_state == 3'd4; + +assign power_enable = sensor_state != 3'd0; + +logic [7:0] rom_end = sensor_state == 3'd1 ? 8'd2 : sensor_state == 3'd3 ? 8'd57 : sensor_state == 3'd6 ? 8'd0 : 8'd0; +logic [24:0] previous_rom = rom_counter == 8'd0 ? 25'd0 : sensor_state == 3'd1 ? PRE_STANDBY[rom_counter - 1'd1] : sensor_state == 3'd3 ? PRE_STREAM[rom_counter - 1'd1] : sensor_state == 3'd6 ? POST_STREAM[rom_counter - 1'd1] : 25'd0; +logic [24:0] current_rom = sensor_state == 3'd1 ? PRE_STANDBY[rom_counter] : sensor_state == 3'd3 ? PRE_STREAM[rom_counter] : sensor_state == 3'd6 ? POST_STREAM[rom_counter] : 25'd0; + +always @(posedge clk_in) +begin + case (sensor_state) + 3'd0: begin + if (mode != 2'd0) + sensor_state <= 3'd1; + end + 3'd1, 3'd3, 3'd6: begin + if (interrupt || transfer_ready) + begin + // Catch write nacks + if (interrupt && (address_err || (mode == 1'd0 && nack))) + begin + transfer_start <= 1'b0; + transfer_continues <= 1'b0; + nack_err <= 1'd1; + sensor_state <= 3'd7; + end + else if (byte_counter == 2'd0) // Address MSB + begin + if (rom_counter != 8'd0 && previous_rom[24] && previous_rom[7:0] != data_rx) // Last read does not match expected + begin + byte_counter <= 2'd0; + rom_counter <= 8'd0; + if (sensor_state == 3'd1) // was a model error + model_err <= 1'd1; + sensor_state <= 3'd7; + end + else + begin + transfer_start <= 1'd1; + transfer_continues <= 1'd1; + i2c_mode <= 1'd0; + data_tx <= current_rom[23:16]; + byte_counter <= 2'd1; + end + end + else if (byte_counter == 2'd1) // Address LSB + begin + transfer_start <= 1'd0; + transfer_continues <= !current_rom[24]; + i2c_mode <= 1'd0; + data_tx <= current_rom[15:8]; + byte_counter <= 2'd2; + end + else if (byte_counter == 2'd2) // Register read or write + begin + transfer_start <= current_rom[24]; + transfer_continues <= 1'd0; + i2c_mode <= current_rom[24]; + data_tx <= current_rom[7:0]; + if (rom_counter == 8'd2) // Last write about to begin + begin + byte_counter <= 2'd0; + rom_counter <= 8'd0; + if (sensor_state == 3'd5) + sensor_state <= 3'd4; // Modifications complete + else if (sensor_state == 3'd6) + sensor_state <= mode == 2'd1 ? 3'd2 : 3'd0; // Either go to standby or power off + else + sensor_state <= sensor_state + 1'd1; // Pre-standby and Pre-stream + end + else + begin + byte_counter <= 2'd0; + rom_counter <= rom_counter + 1'd1; + end + end + end + end + 3'd2: begin + if (mode == 2'd0) + sensor_state <= 3'd0; + else if (mode == 2'd2) + sensor_state <= 3'd3; + else + sensor_state <= 3'd2; + end + 3'd4: begin + if (mode != 2'd2) + sensor_state <= 3'd6; + else + sensor_state <= 3'd4; + end + 3'd5: begin // Not entered, modify support still WIP + end + 3'd7: begin + if (mode == 2'd0) + begin + model_err <= 1'd0; + nack_err <= 1'd0; + sensor_state <= 3'd0; + end + end + endcase +end + +endmodule diff --git a/test/Manifest.py b/test/Manifest.py new file mode 100644 index 0000000..ffaa43e --- /dev/null +++ b/test/Manifest.py @@ -0,0 +1,7 @@ +files = [ + "imx219_tb.sv" +] + +modules = { + "local" : [ "../src/" ], +} diff --git a/test/imx219_tb.sv b/test/imx219_tb.sv new file mode 100644 index 0000000..5d56b1e --- /dev/null +++ b/test/imx219_tb.sv @@ -0,0 +1,60 @@ +module imx219_tb(); + +// Initially, lines are grounded +logic clock_p = 0; +logic clock_n = 0; +always +begin + #2ns; + clock_p <= ~clock_p; + clock_n <= clock_p; +end + +logic [1:0] data_p = 2'd0; +logic [1:0] data_n; +assign data_n = ~data_p; + +logic clk_in; +wire scl; +wire sda; +logic [1:0] mode; +logic [1:0] resolution; +logic format; +logic ready; +logic power_enable; +logic model_err; +logic nack_err; + +imx219 imx219 ( + .clk_in(clk_in), + .scl(scl), + .sda(sda), + // 0 = Power off + // 1 = Software standby + // 2 = Streaming + .mode(mode), + + // 0 = 3280x2464 + // 1 = 1920x1080 + // 2 = 1640x1232 + // 3 = 640x480 + .resolution(resolution), + // 0 = RAW8 + // 1 = RAW10 + .format(format), + + // Goes high when inputs match sensor state + // Changing inputs when the sensor isn't ready could put the sensor into an unexpected state + .ready(ready), + .power_enable(power_enable), + // IMX219 Model ID did not match + .model_err(model_err), + .nack_err(nack_err) +); + +initial +begin + $finish; +end + +endmodule