-
Notifications
You must be signed in to change notification settings - Fork 0
Program Counter
The Program Counter (PC) is the component responsible for holding the memory address of the current and next instruction. It also handles jumps, load/store addresses, and system-level overrides.
The PC receives all of its regular inputs from the left and outputs its values on the right. The upper part contains the CSR interface, which provides overrides for certain control signals and supplies the CSR Controller with important information about the PC.
The PC contains four outputs of general use. The first one is the Pad Address, which connects directly to the IO Devices through the Address Bus. This output can contain values for different functions depending on the context. During the instruction fetch process, it outputs the address for the next instruction. During load and store instructions, it forwards an address calculated internally from its inputs. In case of a system load, an address coming from the CSR Controller is used instead.
Then there are two outputs, one for each register of the PC. The "next" one is used for storing the address after a JAL or JALR instruction, while the "current" one is used for the AUIPC instruction or for some operations of the CSR Controller.
Finally, there is the byte offset, which indicates the starting byte of the data in load operations. This is a remnant from the RISC I architecture, where the load instruction collects the whole word from memory, even if the instruction only requires a short or byte value. If the byte is at the start of the word, then the byte offset is 0; otherwise, it can be offset by up to 3 bytes for byte loads, and up to 2 bytes for short loads. This behavior will be further explained in the Input Buffer Chapter.
The image above shows the internal circuit of the Program Counter. The circuit has been divided into three parts.
The first part is responsible for address calculations used in jumps, branches, and loads. A small adder is used to add either the current PC or the value from the A Bus to the immediate value. If the CSR Controller is present, this result is transmitted to it so that misaligned addresses can be properly handled.
Having the CSR Controller also activates the second part of the circuit (highlighted in red). This section provides additional control for jumps and loads initiated by the CSR Controller. If the CSR Controller is not present, this part can be ignored or even removed entirely—though some signals may need to be reconnected.
The upper portion of the remaining circuit contains the next and current registers. The next register is implemented as a counter, meaning it has extra input signals that allow it to automatically increment when a write signal is active and no jump is being executed. To perform jumps, the next PC receives the jump target address from the calculated result (or from the system target address if the CSR Controller is present).
Note that, since instructions in DRISC-V are always word-aligned (4 bytes), the two least significant bits of the counter are omitted. However, during load/store instructions (signaled by the Forward Address signal), these two bits are stored in a special Byte Offset register, which connects to the corresponding output mentioned earlier.
One of the signals visible in the middle of the circuit is Use Offset. This is enabled during store instructions because, unlike loads, store operations involving data smaller than a word are not necessarily word-aligned. In such cases, the full calculated address is forwarded to the address bus.
timescale 1s/1s
//Inputs and Outputs
module program_counter(
input reset,
input clock,
input write,
input jump,
input pc_relative,
input use_offset,
input forward_address,
input [31:0] immediate,
input [31:0] address_in,
input system_jump,
input system_load,
input [31:0] system_address_target,
output reg [1:0] data_offset,
output [31:0] calculated_address,
output [31:0] next,
output [31:0] current,
output [31:0] address_bus
);
//Registers
reg [29:0] next_reg;
reg [29:0] current_reg;
//Part 1 of the circuit
assign calculated_address = pc_relative ? current + immediate : address_in + immediate;
//forces the output to be 32 bit
assign next = {next_reg, 2'b00};
assign current = {current_reg, 2'b00};
//the output selection for the address bus
assign address_bus = forward_address ?
(system_load ? system_address_target :
use_offset ? calculated_address : {calculated_address[31:2], 2'b00}) :
{next_reg, 2'b00};
always@(posedge clock) begin
if(reset) begin
current_reg <= 30'b0;
next_reg <= 30'b0;
data_offset <= 2'b0;
end
//Here you can see the update behavior of the registers
else if(write) begin
if(system_jump) next_reg <= system_address_target[31:2];
else if(jump) next_reg <= calculated_address[31:2];
else next_reg <= next_reg + 1;
current_reg <= next_reg;
end
if(forward_address) begin
data_offset <= calculated_address[1:0];
end
end
endmodule```-
- 1.1 Introduction
- 1.2 RISC-V Implementation
- 1.2.1 Available Instruction Set
- 1.2.2 Available Non-ISA Features
-
- 2.1 ALU
- 2.2 Register File
- 2.3 Program Counter
- 2.4 Input Buffer
- 2.5 RAM
- 2.6 Operation Controller
- 2.7 CSR Controller
-
- 3.1 Input Devices
- 3.1.1 Keyboard
- 3.1.2 Switches and Joystick
- 3.1.3 Random Number Generator
- 3.1.4 Real-Time Device
- 3.2 Output Devices
- 3.2.1 Screen
- 3.2.2 Terminal
- 3.2.3 Software Interrupt Register
- 3.1 Input Devices