Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/ip-briefs/clint_module_brief_v0.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CLINT - Module Brief (v0.3)

**Owner:** Gavin Wiese
**RTL:** rtl/irq/clint.sv

### Purpose & Role
The Core Local Interruptor (CLINT) is placed near the CPU core, as interrupts are sent directly to the core via memory. The CLINT provides two types of interrupts: **timer-based** (`timer_irq_o`) and **software-based** (`soft_irq_o`). It ensures that events from the timer or software are recognized promptly by the CPU.

### Parameters

- Timer Width: 64 bits (`mtime` and `mtimecmp` registers)
- Software interrupt width: 1 bit (`msip`)
- Software interrupt output width: 1 bit (`soft_irq_o`)
- Timer interrupt output width: 1 bit (`timer_irq_o`)

### Interfaces (Ports)

| **Signal** | **Dir** | **Width** | **Description** |
|----------------|---------|-----------|----------------------------------|
| mtime_o | out | 64 | Current timer value |
| timer_irq_o | out | 1 | Timer interrupt output |
| soft_irq_o | out | 1 | Software interrupt output |
| clk_i | in | 1 | System clock |
| rst_ni | in | 1 | Active-low reset |
| msip_we | in | 1 | Write enable for `msip` register |
| msip_wdata | in | 1 | Data to write to `msip` |
| mtimecmp_we | in | 1 | Write enable for `mtimecmp` register |
| mtimecmp_wdata | in | 64 | Data to write to `mtimecmp` |

### Reset/Init

An active-low reset (`rst_ni`) is used for the CLINT. When reset is asserted (`rst_ni = 0`), all internal registers—`mtime`, `mtimecmp`, and `msip`—are cleared to 0, and output signals (`mtime_o`, `timer_irq_o`, `soft_irq_o`) reflect these reset values. All values are reset synchronously with the system clock. Once reset is deasserted (`rst_ni = 1`), the `mtime` counter begins incrementing every clock cycle, and the interrupt outputs (`timer_irq_o` and `soft_irq_o`) update according to the current register states.

### Behavior and Timing

`mtime` increments by one on every rising edge of the system clock. When `mtime` becomes greater than or equal to `mtimecmp`, the timer interrupt output `timer_irq_o` is asserted and remains asserted until a new, greater `mtimecmp` value is written. The software interrupt output `soft_irq_o` directly reflects the state of the `msip` register. All operations are synchronous with the system clock, and outputs update on the cycle following their trigger conditions.

### Programming Model

The CLINT exposes three memory-mapped registers:

- **`msip`** – Software interrupt pending. Writing a `1` to bit 0 asserts `soft_irq_o` until cleared.
- **`mtimecmp`** – Timer compare register. When the internal 64-bit counter `mtime` reaches or exceeds this value, `timer_irq_o` is asserted until a new, greater value is written.
- **`mtime`** – A 64-bit free-running counter that increments at the system clock rate. Software can read this register for timing purposes.

All registers are accessible via write-enable and data inputs (`*_we`, `*_wdata`).

### Errors/IRQs

| **IRQ** | **Source** | **How it's triggered** | **How it's cleared** |
|----------------|------------|--------------------------------|------------------------------------------------|
| timer_irq_o | mtimecmp | Asserted when mtime >= mtimecmp | Cleared when a new, greater mtimecmp is written |
| soft_irq_o | msip | Reflects the value of the msip register | Cleared by writing 0 to msip |

The CLINT does not generate additional error signals.

### Performance Targets

- `mtime` increments every clock cycle in sync with the system clock.
- Timer and software interrupts (`timer_irq_o` and `soft_irq_o`) are asserted within one clock cycle of their triggering condition.

### Dependencies

The CLINT depends on `clk_i` to increment `mtime` and `rst_ni` to initialize internal registers. Write-enable (`*_we`) and write-data (`*_wdata`) inputs are required to update the `msip` and `mtimecmp` registers. It must be connected to the CPU core so software can receive the interrupt outputs.

### Verification Links

Verification for the CLINT is planned through simulation testbenches to confirm correct behavior of the internal 64-bit timer (`mtime`), timer compare (`mtimecmp`), and software interrupt (`msip`) functionality. Testbenches will ensure that `timer_irq_o` and `soft_irq_o` assert under the correct conditions and that the module responds correctly to reset (`rst_ni`).
69 changes: 69 additions & 0 deletions docs/ip-briefs/plic_module_brief_v0.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# PLIC - Module Brief (v0.3)

**Owner:** Gavin Wiese
**RTL:** rtl/irq/plic.sv

### Purpose & Role
The Platform-Level Interrupt Controller (PLIC) is placed near the CPU core. The PLIC manages and prioritizes external interrupt requests from up to 32 sources, forwarding only the highest-priority pending interrupt to the CPU. This allows the processor to efficiently handle asynchronous external events.

### Parameters

- Number of interrupt sources: 32 (`NSOURCES`)
- Priority field width per source: 3 bits (`PRIO_WIDTH`)
- Interrupt ID width for claim/complete operations: `$clog2(NSOURCES)`

### Interfaces (Ports)

| **Signal** | **Dir** | **Width** | **Description** |
|----------------------|---------|-----------|----------------------------------------------------|
| clk_i | in | 1 | System clock |
| rst_ni | in | 1 | Active-low asynchronous reset |
| src_i | in | 32 | External interrupt sources |
| priority_wdata | in | 96 | Data to write to all priority registers (32 × 3) |
| priority_we | in | 1 | Write enable for priority registers |
| enable_wdata | in | 32 | Data to write to enable register |
| enable_we | in | 1 | Write enable for enable register |
| claim_wdata | in | 5 | Claim complete input |
| claim_we | in | 1 | Write enable for claim completion |
| ext_irq_o | out | 1 | Interrupt output to the CPU core |
| claim_o | out | 5 | Current claimed interrupt ID |

### Reset/Init

An active-low asynchronous reset (`rst_ni`) is used for the PLIC. When reset is asserted (`rst_ni = 0`), all internal registers—including `priorities`, `enable`, `pending`, and `claim`—are cleared to 0, and the output signal `ext_irq_o` is deasserted.

### Behavior and Timing

The PLIC continuously monitors the 32 `src_i` interrupt lines. When one or more enabled interrupts are pending, the highest-priority source is selected, and `ext_irq_o` is asserted to signal the CPU core. Once the CPU completes the interrupt (signaled via `claim_we`), the pending bit for that interrupt is cleared and `ext_irq_o` deasserts. All operations are synchronous with the system clock, and `ext_irq_o` asserts one clock cycle after the conditions are met.

### Programming Model

The PLIC provides three sets of registers controlled via simple write-enable/data inputs:

- **`priority`** – Stores the priority of each interrupt source. Higher values indicate higher priority. Updated via `priority_wdata` and `priority_we`.
- **`enable`** – Determines which interrupt sources are enabled. Updated via `enable_wdata` and `enable_we`.
- **`claim`** – Contains the currently claimed interrupt ID. Writing to this register with `claim_wdata` and `claim_we` signals completion, clearing the pending bit.

All registers are accessible through the `_we` / `_wdata` inputs in this bus-free implementation.

### Errors/IRQs

| **IRQ** | **Source** | **Trigger** | **Clear** |
|------------|-----------|----------------------------------|-------------------------------------|
| ext_irq_o | src_i | One or more enabled interrupts pending | Cleared when CPU signals completion via claim input |

The PLIC does not generate additional internal error signals; all interrupts come from external sources.

### Performance Targets

- `ext_irq_o` asserts within one clock cycle of a pending, enabled interrupt being detected.
- All internal registers (priority, enable, claim/complete, pending) update synchronously with the system clock.
- The PLIC can handle all 32 external sources without loss of pending interrupts.

### Dependencies

The PLIC depends on `clk_i` to update internal registers and monitor interrupt sources, and on `rst_ni` to initialize registers. External interrupt lines (`src_i`) provide input events, and the PLIC drives the single interrupt output (`ext_irq_o`) to the CPU core. Register updates are controlled via `_we` / `_wdata` inputs.

### Verification Links

Verification for the PLIC is planned through simulation testbenches to confirm correct behavior of priority handling, enable bits, and the claim/complete mechanism. Testbenches will ensure that `ext_irq_o` asserts for the highest-priority pending interrupt, that pending bits are cleared after a claim/complete operation, and that the module responds correctly to reset (`rst_ni`).
25 changes: 25 additions & 0 deletions rtl/irq/clint.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module clint(
input logic clk_i,
input logic rst_ni,
output logic Timer_irq_o,
input logic [63:0] user_time, //
output logic [63:0] mtime_o, //
output logic msip
);
logic [63:0] mtimecmp;

always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
mtime_o <= 16'd0;
mtimecmp <= 16'd0;
Timer_irq_o <= 1'b0;
msip <= 1'b0;
end else begin
mtime_o <= mtime_o + 1;
mtimecmp <= user_time;
Timer_irq_o <= (mtime_o >= mtimecmp);
msip <= Timer_irq_o;
end
end
endmodule

105 changes: 105 additions & 0 deletions rtl/irq/plic.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module plic #(
parameter NSOURCES = 32,
parameter PRIO_WIDTH = 3
)(
input logic clk_i,
input logic rst_ni,
input logic [NSOURCES-1:0] src_i, // External interrupt sources

// Simple register interface (bus-free)
input logic [NSOURCES*PRIO_WIDTH-1:0] priority_wdata,
input logic priority_we,
input logic [NSOURCES-1:0] enable_wdata,
input logic enable_we,
input logic [$clog2(NSOURCES)-1:0] claim_wdata,
input logic claim_we,

output logic ext_irq_o,
output logic [$clog2(NSOURCES)-1:0] claim_o
);

// -------------------------
// Registers / Internal state
// -------------------------
logic [NSOURCES*PRIO_WIDTH-1:0] priorities; // Interrupt priorities
logic [NSOURCES-1:0] enable; // Enable bits for sources
logic [$clog2(NSOURCES)-1:0] claim; // Claim register
logic [NSOURCES-1:0] pending; // Pending interrupts
logic [$clog2(NSOURCES)-1:0] highestPriorIndex;
logic [PRIO_WIDTH-1:0] tempHighestValue;
logic activeClaim;

assign claim_o = claim;

// -------------------------
// Active low reset
// -------------------------
always_ff @(negedge rst_ni) begin
if (!rst_ni) begin
priorities <= 0;
enable <= 0;
claim <= 0;
pending <= 0;
highestPriorIndex <= 0;
tempHighestValue <= 0;
activeClaim <= 0;
end
end

// -------------------------
// Simple register writes
// -------------------------
always_ff @(posedge clk_i) begin
if (priority_we) begin
priorities <= priority_wdata;
end
if (enable_we) begin
enable <= enable_wdata;
end
if (claim_we) begin
activeClaim <= 0; // Claim complete
end
end

// -------------------------
// Pending interrupt latching
// -------------------------
always_ff @(posedge clk_i) begin
for (int i = 0; i < NSOURCES; i++) begin
pending[i] <= pending[i] | (src_i[i] & enable[i]);
end
end

// -------------------------
// Priority selector
// -------------------------
always_ff @(posedge clk_i) begin
tempHighestValue <= 0;
highestPriorIndex <= 0;
for (int i = 0; i < NSOURCES; i++) begin
if (pending[i] && priorities[i*PRIO_WIDTH +: PRIO_WIDTH] > tempHighestValue) begin
tempHighestValue <= priorities[i*PRIO_WIDTH +: PRIO_WIDTH];
highestPriorIndex <= i[$clog2(NSOURCES)-1:0];
end
end
Comment thread
gavinwiese marked this conversation as resolved.
end

// -------------------------
// Claim logic
// -------------------------
always_ff @(posedge clk_i) begin
if (!activeClaim && tempHighestValue != 0) begin
claim <= highestPriorIndex;
pending[highestPriorIndex] <= 0;
activeClaim <= 1;
end
end

// -------------------------
// IRQ output
// -------------------------
always_ff @(posedge clk_i) begin
ext_irq_o <= activeClaim;
end

endmodule