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\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.