Program counter

What Are Program Counters?

They are simply a register that holds a value. And in this case, the value is the address of the instruction to fetch.

You can think of it as something that takes 2 things: next instruction and reset. There is another input clk that says you can also do something when according to me.

Then what are the actions? If the reset signal is high, then it stores the value 32b'0 or simply resets to 0. This means the next instruction to fetch is at the start of the program, and the program starts from the top again.

Otherwise, if it is low, then PC will now have the next instruction.

What clk does in all of this? Well, when you have always @(posedge clk), then you are simply telling program counter “Hey, whatever you do, just do it when the clock signal is on the rising edge”. Or, when you have always @(negedge clk), then you tell the PC “Hey, do your thing when the clock signal is on the falling edge :D”.

Having a clk is very important in digital logic circuits, this helps us avoid glitches and racing conditions. For now, you can just think that this prevents some errors and provides predictibility.

How to Write a Pc?

Here is a sample code for a simple pc in verilog:

// pc.v
module pc (
    input  wire        clk,        // Clock signal
    input  wire        reset,      // Asynchronous reset, active high
    input  wire [31:0] next_pc,    // Next PC value (from adder/MUX)
    output reg  [31:0] current_pc  // Holds the current PC value
);

  // On the rising edge of clk or an asserted reset...
  always @(posedge clk or posedge reset) begin
    if (reset) 
      current_pc <= 32'b0;        // Initialize PC to 0 after reset
    else
      current_pc <= next_pc;      // Otherwise, load next_pc
  end

endmodule

The current_pc here is just the value our pc holds and next_pc is the address of the next instruction. This is fed from an adder or a MUX, depending on your program logic. If you are doing something linear (a sequential flow), then the next_pc is just current_pc + 4. But if you have some subroutines (which means you are jumping to another branch, or in a simpler term, if you have conditionals like if in your code), then this comes from MUX. To understand more about branching you can read Branching in RISC V

Program counters are main part of fetch cycle along with instruction memory.