ADC with Altera Cyclone FPGA - internal and external (I2C) implementation - Page 1
Forum section: electronics / general

author: Miti
author_rank: Super Contributor
timestamp: May 09, 2020, 10:14:33 pm
content: How would you implement the simplest and most ingenious ADC in Altera Cyclone II - IV, without using an external ADC?It can be 8 - 10 bits, doesn't have to be super linear, just to read the approximate position of a linear potentiometer.I will post my idea so nobody accuses me of asking others to think for me. It is not very ingenious though but, for me as a noob, is easier than reading an I2C ADC.Edit: To add more details, a counter in the FPGA that gets latched when the output of the comparator goes high and some logic that discharges the cap and resets the counter.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: May 09, 2020, 11:04:58 pm
content: I understand LVDS inputs can be reasonable comparators; YMMV. (Varies by family; some FPGAs have shite receivers with just about zero common mode range.) In combination with a CMOS output, you have a S-D ADC possible. Preferably with the CMOS output buffered to VREF, else you're relying on VCCIO and pin output resistance for that. Or in combination with a DAC (of whatever applicable type, parallel or serial), you have a SAR ADC. That should be fine for, eh, maybe 8 bits give or take calibration.Ramp, sure, can be done that way; beware of linearity though. With care, that should be usable up to 10-12 bits, with INL being the worst offender and DNL being pretty smooth. Slow -- low sample rate due to comparing it with a digital counter. (S-D is slow too, but can dither pretty fast without much effort.)As for your pot, are you sure you can't use an encoder instead?Tim

author: BrianHG
author_rank: Super Contributor
timestamp: May 10, 2020, 03:42:27 am
content: Quote from: Miti on May 09, 2020, 10:14:33 pmNope, it identically complicated as reading a fixed address I2C ADC. You just need a little time to think and practice some Verilog coding.Only if the chosen I2C ADC needs internal registers to be set in advance will using the I2C be any more complicated.3 wire ADCs using SPI bus and I2S are even easier to interface with since you don't need to worry about a bidirectional IO pins. Running multiple of these samplers in parallel is extra easy as each sampler uses 1 additional data input pin while the 2 control outputs are shared between the ADCs. And in your Verilog code, you just add an additional serial capture register byte for each additional ADC.(HINT: we are here to help and strengthening your Verilog skills for IO bus controls will have a bigger payout in the long run...)

author: Wiljan
author_rank: Regular Contributor
timestamp: May 10, 2020, 05:25:02 am
content: Why not use the Max10 from Intel? it does have ADC build-in and are cheap. It also have build-in flash and only need 1 voldtage supply

author: BrianHG
author_rank: Super Contributor
timestamp: May 10, 2020, 07:29:33 am
content: Quote from: Wiljan on May 10, 2020, 05:25:02 amDollar for Dollar, the Max10 has a lower density than the Cyclone's. However, for smaller designs, or where budget isn't a concern, it can be very useful.

author: Miti
author_rank: Super Contributor
timestamp: May 10, 2020, 07:49:40 pm
content: Quote from: BrianHG on May 10, 2020, 03:42:27 amI really want to add I2C to my project and I thought that's for a later date and higher level.But with that very generous offer to help me improve my Verilog coding skills, you convinced me. Thank you!I will go with an I2C ADC, for example MCP3021 plus MCP6021 input buffer.I have a Terasic DE1 Altera Cyclone II development board and for the beginning I will be using an EEPROM with known content. If I can get to reading the content of any address in that EEPROM and display the address and the content on the 4 x 7 segment displays, I can read from the ADC.I've attached a sample code for that board and an I2C library that I found the most appropriate and readable, in my opinion, for this application. I will try to understand and add comments to the code.For the beginning, I will strip the code to the DE1_Default top level only and I will try adding the I2C module "i2c_master.v" and "read_eeprom.v" as higher level that uses the i2c_master.v to read from an address and display on the LED 7 segment.

author: BrianHG
author_rank: Super Contributor
timestamp: May 11, 2020, 01:30:17 am
content: Ok Miti,You have a very complete development board.The example I2C source you provided is written by someone else. Since you do not want to debug and reverse understand their code, we will not be using it.You will be writing an ultra simple I2C interface in Verilog specifically for the MCP3021 so you may actually gain programming skills instead of plug in and tie together third party modules skills.Now before we begin, I need to know a few things:1. Which Quartus are you using?2. Have you ever written anything in Verilog in Quartus?3. Created a 'Sheet Symbol' for such code?4. Placed that generated symbol on a top level block diagram schematic?5. Wired that module to IO pins on your developement board?6. Have you ever simulated your own code before?

author: hamster_nz
author_rank: Super Contributor
timestamp: May 11, 2020, 12:22:07 pm
content: Not saying that this is theright way, but lease complex way for such a simple device as the MCP3021, and with limited HDL skills you could consider stripping things down, making the problem less of a coding issue and more of a data issue.Just use long shift registers to:- control the SCLK- control the tristate on the SDAT line- collect the data bits from from the ADC- control when the data from the serial input shift register are transferred to the serial output (and possibly strobe a "data enable" line).If you then have the correct length shift registers, with the correct values in, the code becomes mundane without any tricky logic traps. All you need is the right length shift registers with the correct patterns in it (and to adjust your constants to match).Not saying that these shift registers below are the correct length, or the correct values are there for anything in particular, but it generates something resembling an I2C waveform and shows the idea.Code:`timescale 1ns / 1psmodule dummy_i2c(input clk,output i2c_sclk,inout i2c_dat, // Externally pulled upoutput [3:0] leds);// Shift registersreg [79:0] data_in_sr;reg [79:0] tristate_sr;reg [79:0] sclk_sr;reg [79:0] data_ready_sr;// For driving the output bufferwire pin_value;wire pin_tristate;// For dividing the clockreg [3:0] counter;// Holds the last read data valuereg [3:0] led_reg;// Attach the input/output pins to the internal registersassign i2c_sclk = sclk_sr;assign pin_tristate = tristate_sr;assign leds = led_reg;initial begin// Patterns for waggling the pins to generate the I2C transaction// 77777777776666666666555555555544444444443333333333222222222211111111110000000000// 98765432109876543210987654321098765432109876543210987654321098765432109876543210tristate_sr <= 80'b10011110000111100001111000000000000111111111111111111111111111111111111111100111;sclk_sr <= 80'b11001100110011001100110011001100110011001100110011001100110011001100110011001111;data_ready_sr <= 80'b00000000000000000000000000000000000000000000000000000000000000000000000000000001;counter <= 4'b0000;endalways @ (posedge clk) begin// Divide the system CLK by 5, making the serial clk 1/20th the system clock.if(counter == 4'b0101) begin// If we should have valid data in the shift register then save it.if(data_ready_sr == 1'b1) beginled_reg = { data_in_sr, data_in_sr, data_in_sr, data_in_sr };end// Move all the shift registers along on bit.tristate_sr <= { tristate_sr[78:0], tristate_sr };sclk_sr <= { sclk_sr[78:0], sclk_sr };data_ready_sr <= { data_ready_sr[78:0], data_ready_sr };// Capture incoming data in the input shift registerdata_in_sr <= { data_in_sr[78:0], pin_value };counter <= 4'b0;end else begincounter <= counter + 1;endend// This is for an Xilinx part, but the equivilent will exist for Altera/IntelIOBUF #(.DRIVE(12), // Specify the output drive strength.IOSTANDARD("DEFAULT"), // Specify the I/O standard.SLEW("SLOW") // Specify the output slew rate) IOBUF_inst (.O(pin_value), // Buffer output.IO(i2c_dat), // Buffer inout port (connect directly to top-level port).I(0), // Buffer input.T(pin_tristate) // 3-state enable input, high=input, low=output);endmodule

author: Miti
author_rank: Super Contributor
timestamp: May 11, 2020, 12:44:42 pm
content: Quote from: BrianHG on May 11, 2020, 01:30:17 amSee here for simulation issues:https://www.eevblog.com/forum/fpga/which-cyclone-pll-implementation-is-better/

author: BrianHG
author_rank: Super Contributor
timestamp: May 11, 2020, 10:01:20 pm
content: Ok, lets start.We will begin doing this 'Extra Dumb' style, so smart experienced ones out there please be patient as we will convert all the 'IF's to case statements and improve sequencing lateron. The goal is first to make Miti write this first interface himself.Step 1:Make the Verilog module (MCP3021_reader) inst with inputs and outputs, IEINPUTS -> clk, reset, conv, SCL_in, SDA_inOutputs regs -> SCL_oe, SCL_out, SDA_oe, SDA_out, adc_val (10bit) , adc_rdy (strobes high when ready)(I listed the SCL and SDA as ins and outs, + bidir pin tristate controls out for both since we may allow full I2C bus release.)Since we need to run the I2C at a lower frequency, we want 2 adjustable parameter for the module:#1- CLK_IN_HZ#2- I2C_SCL_HZStart you module, use as an example setup my RS232 transceiver posted here:https://www.eevblog.com/forum/fpga/verilog-rs232-uart-and-rs232-debugger-source-code-and-educational-tutorial/msg2801394/#msg2801394(We will be programming with similar, yet even simpler coding techniques to make this work)For now,1. Make a synchronous reset defaults for the output pins.2. Make a period counter (call it I2C_period[23:0] ) with enough bits to operate as slow as 2 cycles per second if you use you dev board's 25Mhz clock.3. Make the 'adc_rdy' output reg cycle/invert it's value once every period count.4. Make a symbol for the 'MCP3021_reader', and wire it into your dev-board's Cyclone so that the reset is button actuated and the adc_rdy will cycle an led.5. Test and post the .sv file here and we will add the sequencer to I2C communicate with the MCP3021.When I say test, 2 steps,A) set the 2 parameters so the led will blink as a speed you can visually seeB) simulate so that you can see the 'adc_rdy' cycle, BUT change the parameters so that this cycling happens around every 4-16 clocks, not once every 25million clock cycles.make your code similar to :Code:always @ (posedge clk) beginif (reset) beginend else beginendend // posedge clkendmodule...

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 12, 2020, 12:07:04 am
content: I would personally probably implement that as a sigma-delta ADC. I already have a generic sigma-delta modulator in VHDL with which I have implemented DACs. Implementing an ADC on top of that would be relatively simple. All I'd need would be a couple external parts: typically a comparator and a couple passives.

author: BrianHG
author_rank: Super Contributor
timestamp: May 12, 2020, 01:31:32 am
content: Quote from: hamster_nz on May 11, 2020, 12:22:07 pmMy first step if a little more complex above a simple 1 unified serial pattern. Though, I did say above that interfacing with a MCP3021 would be just as simple, or even simpler than making a sigma-delta ADC converter from scratch.We begin with a period 100/400/1000/any KHz programmable clk with configurable shift segments, then we will separate the segments into the I2C protocol (Start/Stop/ACK/read/write) into separate sequences which will be driven by a master function sequencer so Miti will be able to address any I2C device, including flash I2C eeprom finally adding clock stretching/write eedata timer.

author: Miti
author_rank: Super Contributor
timestamp: May 12, 2020, 11:57:57 am
content: I'm not only looking to get the job done and go away, I also want to learn in the process so I think Brian's approach is good.Thanks Brian, I'll work on it in the next few days, life permitted.

author: AndyC_772
author_rank: Super Contributor
timestamp: May 12, 2020, 12:44:12 pm
content: The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 12, 2020, 02:12:23 pm
content: Quote from: Miti on May 12, 2020, 11:57:57 amUnless I missed something, he talked about interfacing an external ADC? That's certainly something you'll also learn from, but I thought your initial goal was to *implement* an ADC, and not use an external one?Sorry if I missed something in what Brian suggested, though.

author: langwadt
author_rank: Super Contributor
timestamp: May 12, 2020, 03:54:02 pm
content: Quote from: AndyC_772 on May 12, 2020, 12:44:12 pmif it is only for a potmeter input it can be even simpler, similar to the joystick port on old computers.potmeter(+resistor to set a minimum) from Vcc to a cap, cap voltage to a tri-stateble input, when it reaches thresh-hold pulse the pin low to discharge cap

author: langwadt
author_rank: Super Contributor
timestamp: May 12, 2020, 04:05:40 pm
content: Quote from: T3sl4co1l on May 09, 2020, 11:04:58 pmthe comperator for an S-D ADC shouldn't need much common mode, the input is centered +/- hysteresisin a pinch you might be able to get away without the opamp and just use a cap to ground (and an invert of the output)

author: Siwastaja
author_rank: Super Contributor
timestamp: May 12, 2020, 04:16:57 pm
content: Quote from: langwadt on May 12, 2020, 03:54:02 pmYes, I have done this on Cyclone II, don't have the VHDL around here now but I think it was a ridiculously easy one-hour job or so. It worked very well. Controlled a "throttle" signal with a potentiometer on a DIY conversion EV inverter.

author: BrianHG
author_rank: Super Contributor
timestamp: May 12, 2020, 09:33:47 pm
content: Quote from: Miti on May 12, 2020, 11:57:57 amI recommend editing your first opening post and change the thread title to "I2C interface for MCP3021 ADC with Altera Cyclone FPGA" so this thread doesn't get filled with 2 groups of posts making all our heads spin.

author: Miti
author_rank: Super Contributor
timestamp: May 13, 2020, 12:08:56 am
content: Quote from: SiliconWizard on May 12, 2020, 02:12:23 pmYes, you are absolutely right, I started this thinking that I want something easy to implement inside the FPGA, something that I can take on as a beginner in FPGAs. I did not exclude an external I2C device but I saw it as a next level project, and it is next level on my own.With some help from the community, however, it seems more doable, and thanks to Brian for offering that help.I'm still interested in picking the community brains for ideas that I know are abundant and may be handy some time.For example I googled "joystick port on old computers" and Dave's video #1054 came up.So yes, I figured that people would be puzzled and I apologise.

author: Miti
author_rank: Super Contributor
timestamp: May 13, 2020, 12:16:21 am
content: Quote from: AndyC_772 on May 12, 2020, 12:44:12 pmI read somewhere that in the newer Cyclones, the differential inputs don't like common mode but the older ones had really fast comparators that accepted common mode.

author: Miti
author_rank: Super Contributor
timestamp: May 13, 2020, 12:21:20 am
content: Quote from: langwadt on May 12, 2020, 03:54:02 pmThe Cyclone IOs don't have Schmitt trigger inputs so I wonder how clean and repeatable that threshold level is.

author: BrianHG
author_rank: Super Contributor
timestamp: May 13, 2020, 03:17:38 am
content: Quote from: Miti on May 13, 2020, 12:21:20 amThe threshold moves with the VCCIO voltage and ever so slightly with the die temperature.Though Schmitt trigger inputs may prevent bounce or oscillation on a slow rising or slow falling input signal, the truth is that timing and edge wise, Schmitt trigger inputs are actually noisier than regular inputs.

author: Miti
author_rank: Super Contributor
timestamp: May 18, 2020, 01:54:05 am
content: Hey Brian,Here's the module you requested. I can confirm it blinks the LEDR at 2Hz and it resets and stays in reset when I press KEY.Simulation to follow...

author: BrianHG
author_rank: Super Contributor
timestamp: May 18, 2020, 03:22:42 am
content: Ok, perfect, excellent.It is ok to work on solely the 'MCP3021_reader.v' as ascii text here on the forum using the 'Insert Code' icon...Here are my first corrections/updates:Code:module MCP3021_Reader(Clk,// System clockRst_N,// General reset active lowSCL_in,// I2C serial cloc input. Is this necessary for single master?SDA_in,// I2C serial data inputConv,// Start acquisitionSCL_out,// Serial clock outputSDA_out,// Serial data outputSCL_oe,// Output enable for SCLSDA_oe,// Output enable for SDAADC_rdy,// ADC_rdy output - strobes high when data is availableADC_val// 10 bit ADC register. ADC data is available in this register when ADC_Rdy);input Clk;input Rst_N;input SCL_in;inputSDA_in;input Conv;outputreg SCL_out;outputreg SDA_out;outputreg SCL_oe;outputreg SDA_oe;outputreg ADC_rdy;outputreg [9:0] ADC_val;reg[23:0] I2C_Period = 24'd0;// I2C clock dividerparameter CLK_IN_HZ = 16; //25'd24000000;// System clock frequencyparameter I2C_SCL_HZ = 4;// I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)localparam I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;always @ (posedge Clk)beginif (!Rst_N)beginI2C_Period <= I2C_PERIOD_SIZE ;ADC_rdy <= 0;SCL_out <= 1; // I2C default state is all HIGHSCL_oe <= 0; // make I2C tristate by default.SDA_out <= 1; // I2C default state is all HIGHSDA_oe <= 0; // make I2C tristate by default.ADC_rdy <= 0;endelsebeginif ( I2C_Period == 0 )beginSCL_out <= 1; // I2C default state is all HIGHSCL_oe <= 1; // make I2C tristate by default.SDA_out <= 1; // I2C default state is all HIGHSDA_oe <= 1; // make I2C tristate by default.ADC_rdy <= ~ADC_rdy;I2C_Period <= I2C_PERIOD_SIZE;endelsebeginI2C_Period <= I2C_Period - 1;endendend // posedge clkendmoduleOk, now for the changes I made:#1:parameter CLK_IN_HZ = 16; //25'd24000000; // System clock frequencyparameter I2C_SCL_HZ = 4; // I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)localparam I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;In the parameter, removing the " 25'd### " in Quartus changes this:to this:The 'localparam I2C_PERIOD_SIZE' is preferable as we will use a number of localparams to specify the structure and timing of the I2C protocol and it is preferable that all these controls are grouped right at the top of the source code. It's good to avoid any main structural mathematics in the middle of the code.As for the reset, I added all the defaults for the IO pins.As for the loop, I made the test for ' if ( I2C_Period == 0 ) ' so that the loop begins right after the reset without delay.Then I make ' I2C_Period <= I2C_PERIOD_SIZE ; ', and while waiting for the period, I subtract 1 at a time.I setup a simple Quartus 9.1 project using Quartus' built in simulator as seen here:I've attached the project (minus the sub folders and report & programming binaries so that the .zip is tiny). It will open, compile and regenerate the missing files in any Quartus version. Note that it is a virgin project just for simulation. It is not connected to your development board.If you are using a more modern proper simulator like Modelsim, continue to build your own simulation.Next, we will attack the simple 1 dimensional sequencer almost exactly like hamster_nz 's recommendation. Then, break that one down into 2 dimensions where we make use of a higher dimension calling 6 sub dimension RX/TX _I2Cbyte, TX_I2C_start/stop/ack/nak sequences.(The 2 dimension version will then be expandable to do a selection of higher main sequences used for addressing I2C eeproms. Like write_byte and read_byte providing an r_address & w_address and data_in/out bus, then even further if you really want to get advanced like write_block, read_block & block_size for even faster access.)

author: BrianHG
author_rank: Super Contributor
timestamp: May 18, 2020, 03:40:57 am
content: Next step, the 1 dimensional sequencer.First, you will need to add:reg - sequence_counter[].reg - sequence_busylocalparam sequence_size (for now, make it 20)Update the code to reset these & when the reset is released, wait for a Conv.Once the Conv is received, run the period counter sequence for the sequence_size, meaning you should see only 10 cycles of the ADC_rdy and then it should stop until Conv is pulsed again for at least 1 clock cycle. Basically, only take in the Conv when 'sequence_busy' is false, then make 'sequence_busy' active and run the period counter and oscillator until the sequence_counter[] reaches it's end, then clear the sequence busy.(I hope you have a 'MCP3021', around 2 posts from now, it should already work...)

author: Miti
author_rank: Super Contributor
timestamp: May 18, 2020, 10:44:19 pm
content: Quote from: BrianHG on May 18, 2020, 03:40:57 amThisCode:module MCP3021_Reader(Clk,// System clockRst_N,// General reset active lowSCL_in,// I2C serial cloc input. Is this necessary for single master?SDA_in,// I2C serial data inputConv,// Start acquisitionSCL_out,// Serial clock outputSDA_out,// Serial data outputSCL_oe,// Output enable for SCLSDA_oe,// Output enable for SDAADC_rdy,// ADC_rdy output - strobes high when data is availableADC_val// 10 bit ADC register. ADC data is available in this register when ADC_Rdy);input Clk;input Rst_N;input SCL_in;inputSDA_in;input Conv;outputreg SCL_out;outputreg SDA_out;outputreg SCL_oe;outputreg SDA_oe;outputreg ADC_rdy;outputreg [9:0] ADC_val;reg[23:0] I2C_Period = 24'd0;// I2C clock dividerreg[3:0] Sequence_counter;regSequence_busy;parameter CLK_IN_HZ = 16;//25'd24000000;// System clock frequencyparameter I2C_SCL_HZ = 4;// I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)localparam I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;localparamSequence_size = 10;always @ (posedge Clk)beginif (!Rst_N)beginI2C_Period <= I2C_PERIOD_SIZE ;ADC_rdy <= 0;SCL_out <= 1; // I2C default state is all HIGHSCL_oe <= 0; // make I2C tristate by default.SDA_out <= 1; // I2C default state is all HIGHSDA_oe <= 0; // make I2C tristate by default.ADC_rdy <= 0;Sequence_busy <= 0;Sequence_counter <= 0;endif (!Conv && !Sequence_busy)beginSequence_busy <= 1;Sequence_counter <= Sequence_size;endelseif(Sequence_busy)beginif (I2C_Period == 0)beginSCL_out <= 1; // I2C default state is all HIGHSCL_oe <= 1; // make I2C tristate by default.SDA_out <= 1; // I2C default state is all HIGHSDA_oe <= 1; // make I2C tristate by default.ADC_rdy <= ~ADC_rdy;I2C_Period <= I2C_PERIOD_SIZE;if (ADC_rdy)beginSequence_counter <= Sequence_counter -1;endif (Sequence_counter == 0)beginSequence_busy <= 0;ADC_rdy <= 0;endendelsebeginI2C_Period <= I2C_Period - 1;endendend // posedge clkendmoduleQuote from: BrianHG on May 18, 2020, 03:40:57 amNope, not yet. I want to simulate using an EEPROM 24LC08 with known content for now.Datasheet here:http://ww1.microchip.com/downloads/en/devicedoc/21710k.pdf

author: BrianHG
author_rank: Super Contributor
timestamp: May 18, 2020, 11:59:40 pm
content: Quote from: Miti on May 18, 2020, 10:44:19 pmWell, we will still start with it since it is far simpler than the eeprom which needs the ee slaves master address, then subaddress, then with a read, another I2C_start & that master address again, in read mode, then read the data...For the 'MCP3021', you present it's address in read mode, then receive 2 bytes, every time.Don't worry, we will make progress very fast.Ok, for your code, the ' (!Conv && !Sequence_busy) '. I know how with TTL logic, and a lot of input button pullups, you can easily make the mistake of wanting negative logic on the input of your modules.Remember, this module will eventually be buried in logic and controlled by other logic. By default, for example, if you use our 'ADC_rdy' output to feed and Altera fifo or ram module's 'wren' input to accumulate a read cache, if we made it negative logic, you would need to invert that signal's 'WIRE' between modules.Personally, even for reset I prefer positive logic. Here is how I deal with negative logic inputs:Now, if you prefer to keep your module's 'negative' logic inputs, let me know know. We will change the Rst input in the module back to Rst_n, and change the Conv input to a Conv_n so when you wire this module into a larger design, when feeding it's input ports, you know that these controls are negative.After simulation your design, here is what I get:Step #1 patches: (please update your code so we match)Code:module MCP3021_Reader(Clk,// System clockRst,// General reset active lowSCL_in,// I2C serial cloc input. Is this necessary for single master?SDA_in,// I2C serial data inputConv,// Start acquisitionSCL_out,// Serial clock outputSDA_out,// Serial data outputSCL_oe,// Output enable for SCLSDA_oe,// Output enable for SDAADC_rdy,// ADC_rdy output - strobes high when data is availableADC_val// 10 bit ADC register. ADC data is available in this register when ADC_Rdy);input Clk;input Rst;input SCL_in;input SDA_in;input Conv;outputreg SCL_out;outputreg SDA_out;outputreg SCL_oe;outputreg SDA_oe;outputreg ADC_rdy;outputreg [9:0] ADC_val;reg[15:0] I2C_Period = 16'd0;// I2C clock dividerreg[3:0] Sequence_counter;regSequence_busy;parameter CLK_IN_HZ = 16;//25'd24000000;// System clock frequencyparameter I2C_SCL_HZ = 4;// I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)localparam I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;localparamSequence_size = 10;always @ (posedge Clk)beginif (Rst)begin//I2C_Period <= I2C_PERIOD_SIZE[15:0] ; // This would add a delay after Conv has been asserted before the I2C transaction beginsI2C_Period <= 1'd0 ; // This would begin the I2C transaction immediatelyADC_rdy <= 1'd0 ;SCL_out <= 1'd1; // I2C default state is all HIGHSCL_oe <= 1'd0; // make I2C tristate by default.SDA_out <= 1'd1; // I2C default state is all HIGHSDA_oe <= 1'd0; // make I2C tristate by default.ADC_rdy <= 1'd0;Sequence_busy <= 1'd0;Sequence_counter <= 1'd0;endif (Conv && !Sequence_busy)beginSequence_busy <= 1'd1;Sequence_counter <= Sequence_size[3:0];endelseif(Sequence_busy)beginif (I2C_Period == 16'd0)beginSCL_out <= 1'd1; // I2C default state is all HIGHSCL_oe <= 1'd1; // make I2C tristate by default.SDA_out <= 1'd1; // I2C default state is all HIGHSDA_oe <= 1'd1; // make I2C tristate by default.ADC_rdy <= ~ADC_rdy;I2C_Period <= I2C_PERIOD_SIZE[15:0];if (ADC_rdy)beginSequence_counter <= Sequence_counter - 1'd1;endif (Sequence_counter == 4'd0)beginSequence_busy <= 1'd0;ADC_rdy <= 1'd0;endendelsebeginI2C_Period <= I2C_Period - 1'd1;endendend // posedge clkendmodule#1, I made the module's control inputs positive logic.#2, I shrank the I2C period counter to 16 bits.#3, To get rid of compiler warnings, I added these -> I2C_Period <= I2C_PERIOD_SIZE[15:0];and everywhere here -> ADC_rdy <=1'd0;as well as -> if (Sequence_counter ==4'd0)All throughout the code. It is good to avoid pesky compiler warnings all over the place...Now, you made the inner and outer portions of your loop's behavior backwards. This is fine since I didn't specify how we would be using the sequencer, though the hint was that I said a sequence count of '20' would cycle the ADC_rdy only 10 times. In I2C, we want to specify actions at the I2C SCL_out's rising and falling edge, so we need the specify a sequence counter which has events at each time. In fact, we may want 4x the period events. IE, perform an action at the rise and another action at the fall of the SCL_out output, as well as another action right in the middle on the SCL_out being low and in the middle of it being high as well.Next post in around an hour with a solution...Here is a hint:For the 2x approach, SCL_out <= Sequence_counter;For the 4x approach, SCL_out <= Sequence_counter;Within the main looping sequencer of your code.When defining the size of the sequences, the Sequence_counter = (sequence size *2 or *4) and the preiod counter will be divided by 2 or 4 so that the SCL_out will have the correct speed.Let me check the I2C data sheets. The x2 method should be fine, however, I need to double check how clock stretching works since I've only used it once so long ago. We might just need to go with the x4 method unless you want the x4 just so you have an additional timing slice you may take advantage of if you ever need it.

author: BrianHG
author_rank: Super Contributor
timestamp: May 19, 2020, 07:05:26 am
content: Ok, next step, the dummy sequencer...Add this as the new top of your code:Code:parameter CLK_IN_HZ = 16;// 25000000;// System clock frequencyparameter I2C_SCL_HZ = 4;// 100000; // I2C SCL frequency (100000 for 100KHz or 400000 for 400KHz)localparam I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ/4)-1 ;reg[15:0] I2C_Period = 16'd0;// I2C clock divider// *** We are calling these patterns I2C_start as 1 single long pattern for now. During the next step, these patterns// *** will shrink down to just the I2C_start and we will add Pat_I2C_*****_sda/scl/size for the I2C stop, I2C tx/rx byte.// *** then during the I2C transaction, we will run a higher level sequencer which will choose and fill the correct values// *** for the internal I2C pattern sequencer which will seam everything together.// Sequence function STA s7 s6 s5 s4 s3 s2 s1 R/W ACK r7 r6 r5 r4 r3 r2 r1 r0 ACK r7 r6 r5 r4 r3 r2 r1 r0 NAK STO// Sequence position 111100000000009999999999888888888877777777776666666666555555555544444444443333333333222222222211111111110000000000// ***=SDA&SCL high 321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210// SCL_out ***110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011localparam Pat_I2C_start_sda_out = 114'b000111100000000111111110000111111110000000000000000000000000000000000000000000000000000000000000000000000000000001;localparam Pat_I2C_start_sda_oe = 114'b111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000111;localparam Pat_I2C_start_scl_oe = 114'b111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111;localparam Pat_I2C_start_size = 29*4-3; // 29 I2C clocks for MCP3021 running in a single sequence streamlocalparam Pat_I2C_data_rdy1 = 10*4+2; // 11 Clocks from the endlocalparam Pat_I2C_data_rdy2 = 1*4+2; // This one is temporary for MCP3021 running in a single sequence streamlocalparam Pat_I2C_ack1 = 76 ; // This one is temporary for MCP3021 running in a single sequence streamlocalparam Pat_I2C_ack2 = 40 ; // This one is temporary for MCP3021 running in a single sequence streamlocalparam Pat_I2C_phase_rxd = 2 ; // The is the sample phase position when capturing data bits from the sda_inlocalparam Pat_I2C_phase_txd = 2 ; // The is the shift data out phase position when sending data bits to the sda_outreg [113:0] seq_sda_out, seq_sda_oe, seq_scl_oe;reg [7:0] Sequence_counter;reg Sequence_busy;// localparamSequence_size = 10; replace with "Pat_I2C_start_size"Next, build you sequencer and simulate. Hint, this should be your converter 'IF':Code:if (Conv && !Sequence_busy)beginSequence_busy <= 1'd1;Sequence_counter <= Pat_I2C_start_size[7:0];seq_sda_out <= Pat_I2C_start_sda_out[113:0];seq_sda_oe <= Pat_I2C_start_sda_oe[113:0];seq_scl_oe <= Pat_I2C_start_scl_oe[113:0];endGet rid of the cycling of the ADC_rdy.Lets see your new code and new simulation.

author: BrianHG
author_rank: Super Contributor
timestamp: May 19, 2020, 08:15:07 am
content: Note there was a mistake in my sequence above. I just edited the post and corrected it.

author: BrianHG
author_rank: Super Contributor
timestamp: May 21, 2020, 03:55:54 am
content: @Miti > I will most likely no longer be available to help this June.

author: Miti
author_rank: Super Contributor
timestamp: May 22, 2020, 06:13:52 pm
content: Thanks Brian,I’m swamped at work so I don’t know when I can finalize this. On top of that, some good weather seems to be in the forecast and I missed it so much that I can’t wait to take my bike out a bit in the weekend(s).

author: Miti
author_rank: Super Contributor
timestamp: June 10, 2020, 02:02:14 am
content: Quote from: BrianHG on May 19, 2020, 07:05:26 amI'll admit I don't understand this part. Can someone explain what this part mean?

author: BrianHG
author_rank: Super Contributor
timestamp: June 10, 2020, 02:49:31 am
content: What you are looking at is the 'Position number' in a 1 bit serial stream. Reading vertically, from right side to left, I have:00,01,02,03,04,05,06,07,08,09,10,11,12,... It's actually counting from 113 at the left down to 0 on the right.I just wrote that counter position there for your convenience. Also, once we pass 99, I just didn't add the 100 vertically, I basically truncated the '1' in the '1xx'The text above these 2 lines of numbers just denotes my homemade I2C labels of the transaction.Example :STA = start bit patterns7..s0 = slave address transmition bits 6..0, then the R/W bit.Maybe it would have read better:Code:// Sequence function STA s7 s6 s5 s4 s3 s2 s1 R/W ACK r7 r6 r5 r4 r3 r2 r1 r0 ACK r7 r6 r5 r4 r3 r2 r1 r0 NAK STO// Sequence position 11111111111111// Sequence position 11110000000000999999999988888888887777777777666666666655555555554444444444333333333322222222221111111111// ***=SDA&SCL high 321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210// SCL_out ***110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011As for the SCL_out, I am just illustrating what level the SCL output pin should be at during that counter position, 0 -through- 113, all 114 internal clock cycles. It is shown this way to illustrate that there are 4 internal sequence positions for every single low to high cycle of the output SCL pin.In other words, during sequence position 113 and 112, the SCL pin should be high, during position 111 and 110, the SCL pin should be low, during position 109 and 108, the SCL pin should be once again high.In other words, the sequence position is just a 7 bit counter counting down from 113 to 0, or up, depending on how you code.

author: Miti
author_rank: Super Contributor
timestamp: June 10, 2020, 03:36:39 am
content: Got it now, thanks!The key words were “reading vertically “.

author: Miti
author_rank: Super Contributor
timestamp: June 15, 2020, 12:31:09 am
content: I can't put this together to save my life. I blame it on being very tired, these few weeks were very hard at work. However, since I wanted a Cyclone 4 development board, I ordered this one from Ali:https://www.aliexpress.com/item/32858413588.htmlThere's a link to some demo code there and I downloaded it. What I found there is demo code for reading a temperature sensor, read/write I2C and IR remote code.All the comments are in Chinese but Google does wonders translating Chinese. Last weekend, while waiting for the board, I looked through the code and in few hours, I was able to make all three modules that I mentioned before, working on my Terasic DE1. The code seems very clean to a noob like me.I still don't have an I2C ADC, but reading current address from an EEPROM is very similar with reading from the DAC, except for the device address.I've attached the code, someone may find it useful. There was a small mistake with the ACK in one of the module.You can download the entire code from here: fpga.redliquid.plCode:module I2C_READ(clk,rst_n,scl,sda,data);input clk;//总线时钟 50MHzinput rst_n;//异步复位,低电平有效output scl;//SCL 时钟inout sda;// SDA 数据总线output [15:0] data;//温度数据reg [15:0]data_r;// Temperature (or other data) data registerreg scl;//SCL 总线寄存器reg sda_r;//SDA 总线寄存器reg sda_link;//SDA bus data direction sign:reg [8:0]scl_cnt;//SCL clock generation counterreg [2:0]cnt;//Used to mark SCL clock counterreg [25:0]timer_cnt;//定时器,每隔2s 读取一次温度数据reg [3:0]data_cnt;//数据串并转换寄存器reg [7:0]address_reg;//器件地址寄存器reg [8:0]state;//状态寄存器////////////////////////////////////////////////////////////////////////////////////Process 1, 2, 3: Generate SCL bus clockalways@(posedge clk or negedge rst_n)beginif(!rst_n)scl_cnt <= 9'd0;else if(scl_cnt == 9'd199)scl_cnt <= 9'd0;elsescl_cnt <= scl_cnt + 1'b1;endalways@(posedge clk or negedge rst_n)beginif(!rst_n)cnt <= 3'd5;elsecase(scl_cnt)9'd49: cnt <= 3'd1;//高电平中间9'd99: cnt <= 3'd2;//下降沿9'd149:cnt <= 3'd3;//低电平中间9'd199:cnt <= 3'd0;//上升沿default: cnt <= 3'd5;endcaseend`define SCL_HIG (cnt == 3'd1)`define SCL_NEG (cnt == 3'd2)`define SCL_LOW (cnt == 3'd3)`define SCL_POS (cnt == 3'd0)always@(posedge clk or negedge rst_n)beginif(!rst_n)scl <= 1'b0;else if(`SCL_POS)scl <= 1'b1;else if(`SCL_NEG)scl <= 1'b0;end////////////////////////////////////////////////////////////////////////////////////Process 4: Timer, read temperature data every 1salways@(posedge clk or negedge rst_n)beginif(!rst_n)timer_cnt <= 26'd0;else if(timer_cnt == 26'd6249999)timer_cnt <= 26'd0;elsetimer_cnt <= timer_cnt + 1'b1;end////////////////////////////////////////////////////////////////////////////////////状态机定义parameter IDLE = 9'b0_0000_0000,START = 9'b0_0000_0010,ADDRESS = 9'b0_0000_0100,ACK1 = 9'b0_0000_1000,READ1 = 9'b0_0001_0000,ACK2 = 9'b0_0010_0000,READ2 = 9'b0_0100_0000,NACK = 9'b0_1000_0000,STOP = 9'b1_0000_0000;//`define DEVICE_ADDRESS 8'b1001_0001//器件地址,读操作`define DEVICE_ADDRESS 8'b1010_0001//器件地址,读操作////////////////////////////////////////////////////////////////////////////////////Process 5: State machine descriptionalways@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_r <= 16'd0;sda_r <= 1'b1;sda_link <= 1'b1;state <= IDLE;address_reg <= 15'd0;data_cnt <= 4'd0;endelsecase(state)IDLE:beginsda_r <= 1'b1;sda_link <= 1'b1;if(timer_cnt == 26'd6249999)state <= START;elsestate <= IDLE;endSTART://产生起始信号beginif(`SCL_HIG)beginsda_r <= 1'b0;sda_link <= 1'b1;address_reg <= `DEVICE_ADDRESS;state <= ADDRESS;data_cnt <= 4'd0;endelsestate <= START;endADDRESS://主机对器件进行寻址beginif(`SCL_LOW)beginif(data_cnt == 4'd8)//寻址完成,SDA改变方向,器件准备输出应答讯号beginstate <= ACK1;data_cnt <= 4'd0;sda_r <= 1'b1;sda_link <= 1'b0;endelse//寻址过程中,SDA对器件作为输入beginstate <= ADDRESS;data_cnt <= data_cnt + 1'b1;case(data_cnt)4'd0: sda_r <= address_reg;4'd1: sda_r <= address_reg;4'd2: sda_r <= address_reg;4'd3: sda_r <= address_reg;4'd4: sda_r <= address_reg;4'd5: sda_r <= address_reg;4'd6: sda_r <= address_reg;4'd7: sda_r <= address_reg;default: ;endcaseendendelsestate <= ADDRESS;endACK1://器件输出应答信号beginif(!sda && (`SCL_HIG))state <= READ1;else if(`SCL_NEG)state <= READ1;elsestate <= ACK1;endREAD1://读器件数据,高字节beginif((`SCL_LOW) && (data_cnt == 4'd8))//After reading the high byte data, the SDA changes direction, and the host is ready to output the response signalbeginstate <= ACK2;data_cnt <= 4'd0;sda_r <= 1'b0;sda_link <= 1'b1;endelse if(`SCL_HIG)//读数据过程中,器件作为输出begindata_cnt <= data_cnt + 1'b1;case(data_cnt)4'd0: data_r <= sda;4'd1: data_r <= sda;4'd2: data_r <= sda;4'd3: data_r <= sda;4'd4: data_r <= sda;4'd5: data_r <= sda;4'd6: data_r <= sda;4'd7: data_r <= sda;default: ;endcaseendelsestate <= READ1;endACK2://Host output response signalbeginif(`SCL_LOW)sda_r <= 1'b0;else if(`SCL_NEG)beginsda_r <= 1'b1;sda_link <= 1'b0;state <= READ2;endelsestate <= ACK2;endREAD2://读低字节数据beginif((`SCL_LOW) && (data_cnt == 4'd8))beginstate <= NACK;data_cnt <= 4'd0;sda_r <= 1'b1;sda_link <= 1'b1;endelse if(`SCL_HIG)begindata_cnt <= data_cnt + 1'b1;case(data_cnt)4'd0: data_r <= sda;4'd1: data_r <= sda;4'd2: data_r <= sda;4'd3: data_r <= sda;4'd4: data_r <= sda;4'd5: data_r <= sda;4'd6: data_r <= sda;4'd7: data_r <= sda;default: ;endcaseendelsestate <= READ2;endNACK://主机非应答beginif(`SCL_LOW)beginstate <= STOP;sda_r <= 1'b0;endelsestate <= NACK;endSTOP:beginif(`SCL_HIG)beginstate <= IDLE;sda_r <= 1'b1;endelsestate <= STOP;enddefault: state <= IDLE;endcaseendassign sda = sda_link ? sda_r: 1'bz;assign data = data_r;endmoduleCode:module I2C_Comm(clk,rst_n,sw1,sw2,scl,sda,dis_data,wr,rd);input clk;// 50MHzinput rst_n;//Reset signal, active lowinput sw1,sw2;//Buttons 1, 2, (1 press to perform write operation, 2 press to perform read operation)output scl;// 24C02 clock portinout sda;// 24C02 data portoutput[7:0] dis_data;// Nixie tube display dataoutput wr, rd;//Key detectionreg sw1_r,sw2_r;//Key value latch register, detect key value every 20msreg[20:0] cnt_20ms;//20ms count registerreg[23:0] Delay_cnt;/*always @ (posedge clk or negedge rst_n)if(!rst_n) cnt_20ms <= 20'd0;else cnt_20ms <= cnt_20ms+1'b1;//Keep countingalways @ (posedge clk or negedge rst_n)if(!rst_n) beginsw1_r <= 1'b1;//The key value register is reset, the key value is 1 when no keyboard is pressedsw2_r <= 1'b1;endelse if(cnt_20ms == 21'h1fffff) beginsw1_r <= sw1;//Button 1 value latchsw2_r <= sw2;//Button 2 value latchend*/always @ (posedge clk or negedge rst_n)if(!rst_n) Delay_cnt <= 24'd0;else if(Delay_cnt || !sw1 || !sw2) Delay_cnt <= Delay_cnt+1'b1;//Keep countingalways @ (posedge clk or negedge rst_n)if(!rst_n) beginsw1_r <= 1'b1;//The key value register is reset, the key value is 1 when no keyboard is pressedsw2_r <= 1'b1;endelse if(Delay_cnt == 24'd1) beginsw1_r <= sw1;//Button 1 value latchsw2_r <= sw2;//Button 2 value latchendelse if(Delay_cnt == 24'd20000) beginsw1_r <= 1'b1;//Button 1 value latchsw2_r <= 1'b1;//Button 2 value latchendassign wr = sw1_r;assign rd = sw2_r;//---------------------------------------------//Crossoverreg[2:0] cnt;// cnt=0: scl rising edge, cnt=1: middle of scl high level, cnt=2: falling edge of scl, cnt=3: middle of scl low levelreg[8:0] cnt_delay;//500 cycle counts to generate the clock required by iicreg scl_r;//Clock pulse registeralways @ (posedge clk or negedge rst_n)if(!rst_n) cnt_delay <= 9'd0;else if(cnt_delay == 9'd499) cnt_delay <= 9'd0;//Counting to 10us is the period of scl, which is 100KHzelse cnt_delay <= cnt_delay+1'b1;//Clock countalways @ (posedge clk or negedge rst_n) beginif(!rst_n) cnt <= 3'd5;else begincase (cnt_delay)9'd124:cnt <= 3'd1;//cnt=1: middle of scl high level, used for data sampling9'd249:cnt <= 3'd2;//cnt=2:scl Falling edge9'd374:cnt <= 3'd3;//cnt=3:scl Low level middle, used for data change9'd499:cnt <= 3'd0;//cnt=0:scl Rising edgedefault: cnt <= 3'd5;endcaseendend`define SCL_POS(cnt==3'd0)//cnt=0:scl rising edge`define SCL_HIG(cnt==3'd1)//cnt=1:scl high level middle, used for data sampling`define SCL_NEG(cnt==3'd2)//cnt=2:scl falling edge`define SCL_LOW(cnt==3'd3)//cnt=3:scl low level middle, used for data changealways @ (posedge clk or negedge rst_n)if(!rst_n) scl_r <= 1'b0;else if(cnt==3'd0) scl_r <= 1'b1;//scl signal rising edgeelse if(cnt==3'd2) scl_r <= 1'b0;//scl signal falling edgeassign scl = scl_r;//Generate the clock needed for iic//---------------------------------------------//Need to write the address and data of 24C02`defineDEVICE_READ8'b1010_0111//Address of the addressed device (read operation)`define DEVICE_WRITE8'b1010_0110//Address of the addressed device (write operation)//`defineWRITE_DATA8'b1010_0101//Data written to EEPROM`defineWRITE_DATA8'hA5//Data written to EEPROM`define BYTE_ADDR8'b0000_1011//Write/read address register of EEPROMreg[7:0] db_r;//Data registers transferred on IICreg[7:0] read_data;//Read the data register of EEPROM//---------------------------------------------//Read and write timingparameterIDLE= 4'd0;parameterSTART1= 4'd1;parameterADD1= 4'd2;parameterACK1= 4'd3;parameterADD2= 4'd4;parameterACK2= 4'd5;parameterSTART2= 4'd6;parameterADD3= 4'd7;parameterACK3= 4'd8;parameterDATA= 4'd9;parameterACK4= 4'd10;parameterSTOP1= 4'd11;parameterSTOP2= 4'd12;reg[3:0] cstate;//Status registerreg sda_r;//Output data registerreg sda_link;//Output data sda signal inout direction control bitreg[3:0] num;//always @ (posedge clk or negedge rst_n) beginif(!rst_n) begincstate <= IDLE;sda_r <= 1'b1;sda_link <= 1'b0;num <= 4'd0;read_data <= 8'b0000_0000;endelsecase (cstate)IDLE:beginsda_link <= 1'b1;//Data line sda ​​is outputsda_r <= 1'b1;if(!sw1_r || !sw2_r) begin//One of the SW1 and SW2 keys was presseddb_r <= `DEVICE_WRITE;//Send device address (write operation)cstate <= START1;endelse cstate <= IDLE;//No key is pressedendSTART1: beginif(`SCL_HIG) begin//scl is highsda_link <= 1'b1;//Data line sda ​​is outputsda_r <= 1'b0;//Pull down the data line sda ​​to generate the start bit signalcstate <= ADD1;num <= 4'd0;//num count is clearedendelse cstate <= START1; //Wait for the middle position of scl high level to arriveendADD1:beginif(`SCL_LOW) beginif(num == 4'd8) beginnum <= 4'd0;//num count is clearedsda_r <= 1'b1;sda_link <= 1'b0;//sda set to high impedance state (input)cstate <= ACK1;endelse begincstate <= ADD1;num <= num+1'b1;case (num)4'd0: sda_r <= db_r;4'd1: sda_r <= db_r;4'd2: sda_r <= db_r;4'd3: sda_r <= db_r;4'd4: sda_r <= db_r;4'd5: sda_r <= db_r;4'd6: sda_r <= db_r;4'd7: sda_r <= db_r;default: ;endcase//sda_r <= db_r[4'd7-num];//Send device address, starting from high bitendend//else if(`SCL_POS) db_r <= {db_r[6:0],1'b0};//The device address is shifted left by 1 bitelse cstate <= ADD1;endACK1:beginif(/*!sda*/`SCL_NEG) begin//Note: 24C01/02/04/08/16 device can ignore the acknowledge bitcstate <= ADD2;//Slave response signaldb_r <= `BYTE_ADDR;// 1 addressendelse cstate <= ACK1;//Wait for slave responseendADD2:beginif(`SCL_LOW) beginif(num==4'd8) beginnum <= 4'd0;//num count is clearedsda_r <= 1'b1;sda_link <= 1'b0;//sda set to high impedance state (input)cstate <= ACK2;endelse beginsda_link <= 1'b1;//sda as outputnum <= num+1'b1;case (num)4'd0: sda_r <= db_r;4'd1: sda_r <= db_r;4'd2: sda_r <= db_r;4'd3: sda_r <= db_r;4'd4: sda_r <= db_r;4'd5: sda_r <= db_r;4'd6: sda_r <= db_r;4'd7: sda_r <= db_r;default: ;endcase//sda_r <= db_r[4'd7-num];//Send EEPROM address (start with high bit)cstate <= ADD2;endend//else if(`SCL_POS) db_r <= {db_r[6:0],1'b0};//The device address is shifted left by 1 bitelse cstate <= ADD2;endACK2:beginif(/*!sda*/`SCL_NEG) begin//Slave response signalif(!sw1_r) begincstate <= DATA;//Write operationdb_r <= `WRITE_DATA;//Written dataendelse if(!sw2_r) begindb_r <= `DEVICE_READ;//Send device address (read operation), read the specific address need to perform the following stepscstate <= START2;//Read operationendendelse cstate <= ACK2;//Wait for slave responseendSTART2: begin//Read operation start bitif(`SCL_LOW) beginsda_link <= 1'b1;//sda as outputsda_r <= 1'b1;//Pull up the data line sdacstate <= START2;endelse if(`SCL_HIG) begin//scl is high level middlesda_r <= 1'b0;//Pull down the data line sda ​​to generate the start bit signalcstate <= ADD3;endelse cstate <= START2;endADD3:begin//Send operation addressif(`SCL_LOW) beginif(num==4'd8) beginnum <= 4'd0;//num count is clearedsda_r <= 1'b1;sda_link <= 1'b0;//sda set to high impedance state (input)cstate <= ACK3;endelse beginnum <= num+1'b1;case (num)4'd0: sda_r <= db_r;4'd1: sda_r <= db_r;4'd2: sda_r <= db_r;4'd3: sda_r <= db_r;4'd4: sda_r <= db_r;4'd5: sda_r <= db_r;4'd6: sda_r <= db_r;4'd7: sda_r <= db_r;default: ;endcase//sda_r <= db_r[4'd7-num];//Send EEPROM address (start with high bit)cstate <= ADD3;endend//else if(`SCL_POS) db_r <= {db_r[6:0],1'b0};//The device address is shifted left by 1 bitelse cstate <= ADD3;endACK3:beginif(/*!sda*/`SCL_NEG) begincstate <= DATA;//Slave response signalsda_link <= 1'b0;endelse cstate <= ACK3;//Wait for slave responseendDATA:beginif(!sw2_r) begin//Read operationif(num<=4'd7) begincstate <= DATA;if(`SCL_HIG) beginnum <= num+1'b1;case (num)4'd0: read_data <= sda;4'd1: read_data <= sda;4'd2: read_data <= sda;4'd3: read_data <= sda;4'd4: read_data <= sda;4'd5: read_data <= sda;4'd6: read_data <= sda;4'd7: read_data <= sda;default: ;endcase//read_data[4'd7-num] <= sda;//Read data (start with high bit)end//else if(`SCL_NEG) read_data <= {read_data[6:0],read_data};//Data cycle rightendelse if((`SCL_LOW) && (num==4'd8)) beginnum <= 4'd0;//num count is clearedcstate <= ACK4;endelse cstate <= DATA;endelse if(!sw1_r) begin//Write operationsda_link <= 1'b1;if(num<=4'd7) begincstate <= DATA;if(`SCL_LOW) beginsda_link <= 1'b1;//Data line sda ​​as outputnum <= num+1'b1;case (num)4'd0: sda_r <= db_r;4'd1: sda_r <= db_r;4'd2: sda_r <= db_r;4'd3: sda_r <= db_r;4'd4: sda_r <= db_r;4'd5: sda_r <= db_r;4'd6: sda_r <= db_r;4'd7: sda_r <= db_r;default: ;endcase//sda_r <= db_r[4'd7-num];//Write data (start with high bit)end//else if(`SCL_POS) db_r <= {db_r[6:0],1'b0};//Write data to the left by 1 bitendelse if((`SCL_LOW) && (num==4'd8)) beginnum <= 4'd0;sda_r <= 1'b1;sda_link <= 1'b0;//sda set to high impedancecstate <= ACK4;endelse cstate <= DATA;endendACK4: beginif(/*!sda*/`SCL_NEG) begin//sda_r <= 1'b1;cstate <= STOP1;endelse cstate <= ACK4;endSTOP1:beginif(`SCL_LOW) beginsda_link <= 1'b1;sda_r <= 1'b0;cstate <= STOP1;endelse if(`SCL_HIG) beginsda_r <= 1'b1;//When scl is high, sda generates a rising edge (end signal)cstate <= STOP2;endelse cstate <= STOP1;endSTOP2:beginif(`SCL_LOW) sda_r <= 1'b1;//else if(cnt_20ms==20'hffff0) cstate <= IDLE;else if(Delay_cnt==24'hFFFFFF) cstate <= IDLE;else cstate <= STOP2;enddefault: cstate <= IDLE;endcaseendassign sda = sda_link ? sda_r:1'bz;assign dis_data = read_data;endmoduleCode://Display the pressed numeric keys on the digital tubemodule IR(clk,rst_n,IR,led_cs,led_db);input clk;input rst_n;input IR;output [3:0] led_cs;output [7:0] led_db;reg [3:0] led_cs;reg [7:0] led_db;reg [7:0] led1,led2,led3,led4;reg [15:0] irda_data; // save irda data,than send to 7 segment ledreg [31:0] get_data; // use for saving 32 bytes irda datareg [5:0] data_cnt; // 32 bytes irda data counterreg [2:0] cs,ns;reg error_flag; // 32 bytes data during, the data error flag//----------------------------------------------------------------------------reg irda_reg0; //To avoid metastability and avoid driving multiple registers, this one is not used.reg irda_reg1; //This can be used, the status of irda in the following programreg irda_reg2; //In order to determine the edge of irda, hit the register again. The following program represents the previous state of irdawire irda_neg_pulse; //Determine the falling edge of irdawire irda_pos_pulse; //Determine the rising edge of irdawire irda_chang; //Indeed ╥rda's transition edgereg[15:0] cnt_scan;//Scanning frequency counteralways @ (posedge clk) //Follow registerif(!rst_n)beginirda_reg0 <= 1'b0;irda_reg1 <= 1'b0;irda_reg2 <= 1'b0;endelsebeginled_cs <= 4'b0000; //The bit selection of the nixie tube is onirda_reg0 <= IR;irda_reg1 <= irda_reg0;irda_reg2 <= irda_reg1;endassign irda_chang = irda_neg_pulse | irda_pos_pulse; //IR received signal changes, rising or fallingassign irda_neg_pulse = irda_reg2 & (~irda_reg1); //IR receive signal irda falling edgeassign irda_pos_pulse = (~irda_reg2) & irda_reg1; //IR receive signal irda rising edgereg [10:0] counter; //Frequency division 1750 timesreg [8:0] counter2; //Count the points after frequency divisionwire check_9ms; // check leader 9ms timewire check_4ms; // check leader 4.5ms timewire low; // check data="0" timewire high; // check data="1" time//----------------------------------------------------------------------------//1750 countsalways @ (posedge clk)if (!rst_n)counter <= 11'd0;else if (irda_chang) //irda level jumps, it starts counting againcounter <= 11'd0;else if (counter == 11'd1750)counter <= 11'd0;elsecounter <= counter + 1'b1;//----------------------------------------------------------------------------always @ (posedge clk)if (!rst_n)counter2 <= 9'd0;else if (irda_chang) //irda level jumps, counting starts againcounter2 <= 9'd0;else if (counter == 11'd1750)counter2 <= counter2 +1'b1;assign check_9ms = ((217 < counter2) & (counter2 < 297));//257 In order to increase stability, take a certain rangeassign check_4ms = ((88 < counter2) & (counter2 < 168)); //128assign low = ((6 < counter2) & (counter2 < 26)); // 16assign high = ((38 < counter2) & (counter2 < 58)); // 48//----------------------------------------------------------------------------// generate statemachine state machineparameter IDLE = 3'b000, //Initial stateLEADER_9 = 3'b001, //9msLEADER_4 = 3'b010, //4msDATA_STATE = 3'b100; //transfer dataalways @ (posedge clk)if (!rst_n)cs <= IDLE;elsecs <= ns; //Status bitalways @ ( * )case (cs)IDLE:if (~irda_reg1)ns = LEADER_9;elsens = IDLE;LEADER_9:if (irda_pos_pulse) //leader 9ms checkbeginif (check_9ms)ns = LEADER_4;elsens = IDLE;endelse //complete if---else--- ;Prevent generation latchns =LEADER_9;LEADER_4:if (irda_neg_pulse) // leader 4.5ms checkbeginif (check_4ms)ns = DATA_STATE;elsens = IDLE;endelsens = LEADER_4;DATA_STATE:if ((data_cnt == 6'd32) & irda_reg2 & irda_reg1)ns = IDLE;else if (error_flag)ns = IDLE;elsens = DATA_STATE;default:ns = IDLE;endcase//The output in the state machine is described by a sequential circuitalways @ (posedge clk)if (!rst_n)begindata_cnt <= 6'd0;get_data <= 32'd0;error_flag <= 1'b0;endelse if (cs == IDLE)begindata_cnt <= 6'd0;get_data <= 32'd0;error_flag <= 1'b0;endelse if (cs == DATA_STATE)beginif (irda_pos_pulse) // low 0.56ms checkbeginif (!low) //errorerror_flag <= 1'b1;endelse if (irda_neg_pulse) //check 0.56ms/1.68ms data 0/1beginif (low)get_data <= 1'b0;else if (high)get_data <= 1'b1;elseerror_flag <= 1'b1;get_data[31:1] <= get_data[30:0];data_cnt <= data_cnt + 1'b1;endendalways @ (posedge clk)if (!rst_n)beginirda_data <= 16'd0;led2 <= 8'd0;endelse if ((data_cnt ==6'd32) & irda_reg1)beginled1 <= get_data[7:0]; //Data inverseled2 <= get_data[15:8]; //Data codeled3 <= get_data[23:16];//User codeled4 <= get_data[31:24];end//Display the keys pressed by the remote control on the digital tubealways@(led2)begincase(led2)//Display 0 to 9 on the digital tube8'b01101000: //Code value of remote control board 0led_db=8'b0000_0000; //Show 08'b00110000: //Code value of remote control board 1led_db=8'b0000_0001; //Show 18'b00011000: //遥控板2的码值led_db=8'b0000_00010; //Show 28'b01111010: //遥控板3的码值led_db=8'b0000_0011; //Show 38'b00010000: //遥控板4的码值led_db=8'b0000_0100; //Show 48'b00111000: //遥控板5的码值led_db=8'b0000_0101; //Show 58'b01011010: //遥控板6的码值led_db=8'b0000_0110; //Show 68'b01000010: //遥控板7的码值led_db=8'b0000_0111; //Show 78'b01001010: //遥控板8的码值led_db=8'b0000_1000; //Show 88'b01010010: //遥控板9的码值led_db=8'b0000_1001; //Show 98'b10011000: //Code value of remote control board 100+led_db=8'b0001_0000; //Show 108'b10110000: //Code value of remote control board 200+led_db=8'b0010_0000; //Show 208'b11100000: //Code value of remote control board Vol-led_db=8'hDD; //Show 108'b10101000: //Code value of remote control board Vol+led_db=8'h11; //Show 108'b10010000: //Code value of remote control board EQled_db=8'hE0; //Show E0//When the key is not pressed, F is displayeddefault: led_db=8'b1110_1110;endcaseendendmodule