4
\$\begingroup\$

For now, I have only implemented a simple I2C protocol where only the master transmits the data. Also there is ack_bit for the I2C Address only. For state 5, i.e., data_byte its always acknowledged.

I am just starting with Verilog, and this is my first big project so, please review my code and suggest any changes.

main source code:

 module main(input reset); wire scl,clk; wire sda_line; wire sda_master_enable; master main_master(.reset(reset),.sda_line(sda_line),.scl(scl),.sda_master_enable(sda_master_enable)); slave main_slave(.sda_line(sda_line),.scl(scl),.sda_master_enable(sda_master_enable)); endmodule //////////////////////////////////////////////////////////////////////////////////// module master(input reset, inout sda_line, output reg scl,[2:0]state_out,wire sda_master_enable); wire clk,reset; reg sda,sda_enable = 1; reg [2:0]state = 3'b000; reg [2:0] state_out; reg sda_master_enable; reg [6:0]addr_reg = 7'h69; //69 = 1101001 reg [2:0]count = 3'd6; reg rw_reg = 0; //FOR NOW transmitting data from master; reg [7:0] data_reg = 8'b10001010; reg data_out; reg addr_ack_bit = 1; reg data_ack_bit; reg ack_flag = 0; reg [2:0]data_count = 3'd7; parameter IDLE_STATE = 3'b000, START_STATE = 3'b001, ADDR_STATE = 3'b010, RW_STATE = 3'b011, ADDR_ACK_STATE = 3'b100, DATA_STATE = 3'b101, DATA_ACK_STATE = 3'b110, STOP_STATE = 3'b111; clock_divider master_cd(.i2c_clk(clk)); assign sda_line = (sda_enable) ? sda : 1'bz; // Master drives SDA only when sda_enable = 1 always @(posedge clk) begin if(reset == 0) begin case(state) IDLE_STATE: begin sda<=1;scl<=1; state_out <= IDLE_STATE; state<=START_STATE; end START_STATE: begin sda<=0; state_out <= START_STATE; state<=ADDR_STATE; end ADDR_STATE: begin sda_enable = 1; if(count == 0) begin sda<=addr_reg[count]; state_out <= ADDR_STATE; state<=RW_STATE; count<=3'd6; end else begin sda<=addr_reg[count]; count = count-1; // DATA will work according to sysclk; // you have to configure scl accordingly to match i2c rule; end end RW_STATE: begin sda<=rw_reg; state_out <= RW_STATE; state<=ADDR_ACK_STATE; end ADDR_ACK_STATE: begin sda_enable = 0; state_out <= ADDR_ACK_STATE; end DATA_STATE: begin sda_enable = 1; if(data_count == 0) begin sda<=data_reg[data_count]; state_out <= DATA_STATE; state<=DATA_ACK_STATE; end else begin sda<=data_reg[data_count]; data_count = data_count-1; // DATA will work according to sysclk; // you have to configure scl accordingly to match i2c rule; end end DATA_ACK_STATE: begin sda_enable = 0; data_ack_bit = sda_line; state_out <= DATA_STATE; state <= (data_ack_bit) ? DATA_STATE : STOP_STATE; end STOP_STATE: begin sda_enable <= 1; sda<= 1; scl<=1; state_out <= STOP_STATE; end default: begin sda<=1;scl<=1; end endcase end else if(reset == 1) begin sda<=1; scl <= 1; end state_out <= state; end always @(posedge scl) begin if(state_out == ADDR_ACK_STATE) begin sda = sda_line; // Capture it properly addr_ack_bit = sda; $display("ACK Bit Read: %b", sda); if(addr_ack_bit == 1) begin state <= ADDR_STATE; // Retry address phase if no ACK end else if(addr_ack_bit == 0) begin state <= DATA_STATE; // Proceed to data transmission end end end always @(clk) begin if(state == ADDR_STATE || state == RW_STATE || state == ADDR_ACK_STATE || state == DATA_STATE ||state == DATA_ACK_STATE) begin //Starting of scl after completing starting state; scl <= ~clk; end if(state_out == DATA_ACK_STATE) begin scl <= 1; end end always @(sda_enable) begin sda_master_enable = sda_enable; end endmodule /////////////////////////////////////////////////////////////// module slave(input scl,sda_master_enable,inout sda_line,output addr_data_out,addr_count_out,flag); reg sda,sda_enable = 0; // Controls when slave drives SDA wire clk; reg flag_reg = 1'bz; wire flag; reg [7:0] addr_data = 8'b00000000; reg [7:0] addr_data_out; reg [3:0] addr_count = 4'b1010; //here from 9 to 0 10 states // we require 8 bits (7+1) //+1 bit for starting posedge of scl from Hi-im state; reg [3:0] addr_count_out; reg addr_flag = 0; parameter slave_addr = 8'hD1; assign sda_line = sda_enable ? sda : 1'bz; // To drive the inout net clock_divider master_cd(.i2c_clk(clk)); always @(negedge clk) begin if(flag_reg == 1) begin if(addr_flag == 0) begin if(addr_count <= 9 && addr_count >= 2) begin sda = sda_line; //no non-blocking for sda because we want it right now and here, nd not on next clock cycle addr_data = addr_data | (sda << addr_count-2) ; //same case with addr_data addr_data_out[7:0] <= addr_data[7:0]; addr_count <= addr_count -1; //Some fucked up shit combining bloacking and non blocking is not advisable but for now its giving me correct result so gud!! addr_count_out <= addr_count -1; end else begin addr_count <= addr_count -1; addr_count_out <= addr_count -1; //earlier it was addr_count_out <= addr_count ,,,,which was two step process first addt_count updates in one cycle and then addr_count_out; end end end end always @(negedge sda_line) begin #1; if(scl == 1) flag_reg <= 1; //Starting condition detected end always @(posedge sda_line) begin #1; if(scl == 1) flag_reg <= 0; //stopping condition detected; end always @(negedge sda_master_enable) begin if(addr_flag == 0) begin if(sda_master_enable == 0) begin sda_enable = 1; if (addr_data == slave_addr) begin sda = 1'b0; addr_data <= 8'b00000000; addr_data_out[7:0] <= 8'b00000000; addr_count <= 4'b1010; addr_count_out <= 4'b10; addr_flag<=1; end else begin sda = 1'b1; //NACK addr_data <= 8'b00000000; addr_data_out[7:0] <= 8'b00000000; addr_count <= 4'b1010; addr_count_out <= 4'b1010; addr_flag<=0; //If NACK then resend data from master and re store it in slave end end end else if(addr_flag == 1) begin sda_enable = 1; sda<=1'b0; end end always @(posedge sda_master_enable) begin if(sda_master_enable == 1) begin sda_enable = 0; end end assign flag = flag_reg; //Do notconfuse non blocking /blocking sign to assign sign here "=" simply means they are equal so when ever the reg changes the wire changes; //Above is the good choice to see reg output in testbench waveform since testbench only takes wires:) endmodule //////////////////////////////////////////////////////////// module clock_signal ( output reg val ); initial begin val = 0; forever #5 val = ~val; // 10 nanosecond main clock ;Frequency = 100MHz gud!! // BUT For standard i2c speed we want 100KHz or 10us clock end endmodule ///////////////////////////////////////////////////////////////////////////////// module clock_divider(output i2c_clk); wire ref_clk; wire reset; reg i2c_clk = 1; reg [10:0] counter = 0; clock_signal cd_cs(.val(ref_clk)); always @(posedge ref_clk) begin if(counter == (500)) begin i2c_clk = ~ i2c_clk; counter = 0; end else begin counter = counter + 1; // up counter is always efficient than down counter bcus ripple carry is gud!! end end endmodule ///////////////////////////////////////// 

testbench code:

 module testbench(); wire main_clk; wire i2c_clk; reg reset; wire sda_line, scl; wire [2:0]state_out; wire [3:0] addr_count_out; wire sda_master_enable; wire sda_master_enable; wire scl,sda_line; wire [7:0] addr_data_out; wire flag; // Instantiate the main module main dut1( .reset(reset) ); master dut2(.reset(reset),.sda_line(sda_line),.scl(scl),.state_out(state_out),.sda_master_enable(sda_master_enable)); slave dut5(.scl(scl),.sda_line(sda_line),.addr_data_out(addr_data_out),.addr_count_out(addr_count_out),.flag(flag),.sda_master_enable(sda_master_enable)); clock_signal dut3(.val(main_clk)); clock_divider dut4(.i2c_clk(i2c_clk)); // Test Stimulus initial begin reset = 1; #27000 reset = 0; // Trigger Start Condition #300000 $finish; end // Monitor Outputs initial begin $dumpfile("waveform.vcd"); $dumpvars(0, testbench); end endmodule 

waveform for ACK)

waveform for NACK):

\$\endgroup\$
0

    1 Answer 1

    3
    \$\begingroup\$

    Overview

    You have done a good job with:

    • Partitioning the design and testbench into separate files
    • Dumping waveforms from your testbench to verify and help debug your design
    • Using consistent naming style for your signals
    • Using parameter types for your constants
    • Indenting the code

    It is great that you provided a complete code example so we can easily run a simulation.

    Portability

    It is a good practice to try to compile and run the code on different simulators. Free simulators are available on the EDA Playground website, for example.

    On one simulator I tried, I get a compile error on this line:

    module master(input reset, inout sda_line, output reg scl,[2:0]state_out,wire sda_master_enable); 

    Some simulation software is more forgiving than others; the simulator you used is too forgiving in this case because it should not have allowed you to declare sda_master_enable as both a wire and as a reg a few lines later:

    reg sda_master_enable; 

    In this case reg is the correct type. This can be fixed by removing wire:

    module master(input reset, inout sda_line, output reg scl,[2:0]state_out, sda_master_enable); 

    I get a compile warning on this line:

    wire clk,reset; 

    You already declared reset as an input. There is no need to declare it as a wire as well, and you can remove it from the line:

    wire clk; 

    Similarly for these signals:

    reg [2:0] state_out; reg sda_master_enable; 

    These lines should be deleted. This requires more changes which I will discuss below.

    In the slave module, these lines should be deleted also:

    wire flag; reg [7:0] addr_data_out; reg [3:0] addr_count_out; 

    In the testbench, you have 2 redundant lines:

    wire sda_line, scl; wire scl,sda_line; 

    You should delete one of them.

    This line is repeated twice:

    wire sda_master_enable; 

    Layout

    It would be better to collapse multiple consecutive blank lines into a single blank line. I find it easier to understand code if I can see more of it on my screen at once. For example, in the slave module, there is no need for multiple blank lines before the endmodule line.

    It is great that you use the ANSI-style for module port declarations because it reduces the redundancy of maintaining separate signals lists using the older style.

    Instead of having a very long like of code for the ports, I recommend splitting them onto separate lines. This is a common good practice for Verilog. For example:

    module slave ( input scl, input sda_master_enable, inout sda_line, output reg [7:0] addr_data_out, output reg [3:0] addr_count_out, output flag ); 

    While this is more verbose since it repeats the input and output keywords for each signal, it does make the code more explicit, and it avoids one of the most common Verilog pitfalls associated with range specifications, like you have in the master module. You should change the code to:

    module master ( input reset, inout sda_line, output reg scl, output reg [2:0] state_out, output reg sda_master_enable ); 

    It is great that you used connections-by-name for the module instances. I recommend splitting the instantiations out over several lines, one line per port. In testbench you did this for the dut1 instance already. Let's do it for all instances, for example:

    slave dut5 ( .scl (scl), .sda_line (sda_line), .addr_data_out (addr_data_out), .addr_count_out (addr_count_out), .flag (flag), .sda_master_enable (sda_master_enable) ); 

    This makes the code easier to understand and maintain.

    Comments

    Add comments to describe each module:

    /* I2C slave add details of functionality here: - what features are implemented - what features are not yet implemented */ module slave ( 

    Some comments which appear at the end of lines of code like this:

    sda = sda_line; //no non-blocking for sda because we want it right now and here, nd not on next clock cycle 

    lead to lines that are too long. We should not be forced to do a lot of horizontal scrolling since it detracts from understanding the code. The comment should be moved to a separate line above the code.

    // no non-blocking for sda because we want it right now and here, and not on next clock cycle sda = sda_line; 

    Naming

    The names of the modules should be more unique. slave could be i2c_slave, for example. We typically connect many designs together to form a larger design, and we need the design names to be unique.

    main is a vague name for a module. Also, it is not clear why it is even included because it seems redundant with the master and slave instances in testbench. If you still need main, you should give it a more meaningful name and add comments describing its purpose.

    Reset

    In the master module, you use the reset input signal to reset some of the logic. This is the recommended practice since it allows the design to be portable to both FPGA and ASIC design flows.

    You should add the signal as an input to the clock_divider module as well. You have it as an unused wire now.

    It is common for FPGA flows to initialize registers in the declaration line as you do for the counter signal, but it would be better to use reset.

    As far as partitioning goes, it would be better to remove the clock_signal instance out of the clock_divider, and add a clock input port. Then connect the clock_signal output to the new clock_divider input in the testbench. The clock_signal module is not synthesizable, and it really only belongs in the testbench. This change allows the clock_divider to be synthesizable.

    Nonblocking

    It is considered good practice to use nonblocking assignments (<=) to model sequential logic. This avoids Verilog simulation race conditions. For example, in clock_divider:

    always @(posedge ref_clk) begin if(counter == 500) begin counter <= 0; end else begin counter <= counter + 1; end end 

    Blocking assignments (=) should only be used to model combinational logic.

    Master

    Here are some suggestions for the master module.

    This sensitivity list is good:

    always @(sda_enable) begin 

    But, it is a good practice to use the implicit sensitivity list for combinational logic:

    always @* begin 

    This avoids another common Verilog pitfall of forgetting to add signals that should be there.

    This sensitivity list is unusual:

    always @(clk) begin 

    It implies that you want to describe combinational logic, but it will simulate more like sequential logic. I recommend using the more common:

    always @(posedge clk) begin 

    This makes it clear that it infers sequential logic.

    This line creates a second clock domain in the module:

    always @(posedge scl) begin 

    This is always a tricky situation. Great care needs to be taken to design with multiple domains. However, in this case, there should be no need to do so. You should be able to restructure all your logic to use the same clock, namely clk:

    always @(posedge clk) begin 

    It is not clear why you have a state_out signal as well as a state signal. It is common practice to design an FSM using 2 always blocks:

    • curr_state: Sequential logic: always @(posedge clk)
    • next_state: Combinational logic: always @*

    Designing is an iterative process, especially for a design like I2C, whose specification may seem simple at first, but there are many subtleties to it. It is great that you have only implemented the limited set of features you need for your design.

    What next, you ask? One approach at this point is to try to implement some of these suggestions. The primary goal for now is to simplify the code so that it is a little easier to understand.

    Since you are new to this site, your first instinct might be to update the code in your question. Please refrain from doing so. Instead, follow the advice in the Help Center. For example, you could post a follow-up question.

    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.