mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-22 21:27:10 -04:00
295 lines
10 KiB
Systemverilog
295 lines
10 KiB
Systemverilog
// Copyright 2018 ETH Zurich and University of Bologna.
|
|
// Copyright and related rights are licensed under the Solderpad Hardware
|
|
// License, Version 0.51 (the “License”); you may not use this file except in
|
|
// compliance with the License. You may obtain a copy of the License at
|
|
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
|
// or agreed to in writing, software, hardware and materials distributed under
|
|
// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
|
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations under the License.
|
|
//
|
|
// Author: Florian Zaruba, ETH Zurich
|
|
// Date: 15/07/2017
|
|
// Description: A RISC-V privilege spec 1.11 (WIP) compatible CLINT (core local interrupt controller)
|
|
//
|
|
|
|
// Platforms provide a real-time counter, exposed as a memory-mapped machine-mode register, mtime. mtime must run at
|
|
// constant frequency, and the platform must provide a mechanism for determining the timebase of mtime (device tree).
|
|
|
|
module clint #(
|
|
parameter config_pkg::cva6_cfg_t CVA6Cfg = cva6_config_pkg::cva6_cfg,
|
|
parameter int unsigned AXI_ADDR_WIDTH = 64,
|
|
parameter int unsigned AXI_DATA_WIDTH = 64,
|
|
parameter int unsigned AXI_ID_WIDTH = 10,
|
|
parameter int unsigned NR_CORES = 1, // Number of cores therefore also the number of timecmp registers and timer interrupts
|
|
parameter type axi_req_t = ariane_axi::req_t,
|
|
parameter type axi_resp_t = ariane_axi::resp_t
|
|
) (
|
|
input logic clk_i, // Clock
|
|
input logic rst_ni, // Asynchronous reset active low
|
|
input logic testmode_i,
|
|
input axi_req_t axi_req_i,
|
|
output axi_resp_t axi_resp_o,
|
|
input logic rtc_i, // Real-time clock in (usually 32.768 kHz)
|
|
output logic [NR_CORES-1:0] timer_irq_o, // Timer interrupts
|
|
output logic [NR_CORES-1:0] ipi_o // software interrupt (a.k.a inter-process-interrupt)
|
|
);
|
|
// register offset
|
|
localparam logic [15:0] MSIP_BASE = 16'h0;
|
|
localparam logic [15:0] MTIMECMP_BASE = 16'h4000;
|
|
localparam logic [15:0] MTIME_BASE = 16'hbff8;
|
|
|
|
localparam AddrSelWidth = (NR_CORES == 1) ? 1 : $clog2(NR_CORES);
|
|
|
|
// signals from AXI 4 Lite
|
|
logic [AXI_ADDR_WIDTH-1:0] address;
|
|
logic en;
|
|
logic we;
|
|
logic [7:0] be;
|
|
logic [63:0] wdata;
|
|
logic [63:0] rdata;
|
|
|
|
|
|
// bit 11 and 10 are determining the address offset
|
|
logic [15:0] register_address;
|
|
assign register_address = address[15:0];
|
|
// actual registers
|
|
logic [63:0] mtime_n, mtime_q;
|
|
logic [NR_CORES-1:0][63:0] mtimecmp_n, mtimecmp_q;
|
|
logic [NR_CORES-1:0] msip_n, msip_q;
|
|
// increase the timer
|
|
logic increase_timer;
|
|
|
|
// -----------------------------
|
|
// AXI Interface Logic
|
|
// -----------------------------
|
|
axi_lite_interface #(
|
|
.AXI_ADDR_WIDTH ( AXI_ADDR_WIDTH ),
|
|
.AXI_DATA_WIDTH ( AXI_DATA_WIDTH ),
|
|
.AXI_ID_WIDTH ( AXI_ID_WIDTH ),
|
|
.axi_req_t ( axi_req_t ),
|
|
.axi_resp_t ( axi_resp_t )
|
|
) axi_lite_interface_i (
|
|
.clk_i ( clk_i ),
|
|
.rst_ni ( rst_ni ),
|
|
.axi_req_i ( axi_req_i ),
|
|
.axi_resp_o ( axi_resp_o ),
|
|
.address_o ( address ),
|
|
.en_o ( en ),
|
|
.we_o ( we ),
|
|
.be_o ( be ),
|
|
.data_i ( rdata ),
|
|
.data_o ( wdata )
|
|
);
|
|
|
|
// -----------------------------
|
|
// Register Update Logic
|
|
// -----------------------------
|
|
// APB register write logic
|
|
always_comb begin
|
|
mtime_n = mtime_q;
|
|
mtimecmp_n = mtimecmp_q;
|
|
msip_n = msip_q;
|
|
// RTC says we should increase the timer
|
|
if (increase_timer)
|
|
mtime_n = mtime_q + 1;
|
|
|
|
// written from APB bus - gets priority
|
|
if (en && we) begin
|
|
case (register_address) inside
|
|
[MSIP_BASE:MSIP_BASE+4*NR_CORES]: begin
|
|
msip_n[$unsigned(address[AddrSelWidth-1+2:2])] = wdata[32*address[2]];
|
|
end
|
|
|
|
[MTIMECMP_BASE:MTIMECMP_BASE+8*NR_CORES]: begin
|
|
if (CVA6Cfg.XLEN == 32) begin
|
|
if (be[3:0] == 4'hf)
|
|
mtimecmp_n[$unsigned(address[AddrSelWidth-1+3:3])][31:0] = wdata[31:0];
|
|
else
|
|
mtimecmp_n[$unsigned(address[AddrSelWidth-1+3:3])][63:32] = wdata[63:32];
|
|
|
|
end else begin
|
|
mtimecmp_n[$unsigned(address[AddrSelWidth-1+3:3])] = wdata;
|
|
end
|
|
end
|
|
|
|
[MTIME_BASE:MTIME_BASE+4]: begin
|
|
if (CVA6Cfg.XLEN == 32) begin
|
|
if (address[2:0] == 3'h0)
|
|
mtime_n[31:0] = wdata[31:0];
|
|
else begin
|
|
if (address[2:0] == 3'h4)
|
|
mtime_n[63:32] = wdata[63:32];
|
|
end
|
|
end else begin
|
|
mtime_n = wdata;
|
|
end
|
|
end
|
|
default:;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// APB register read logic
|
|
always_comb begin
|
|
rdata = 'b0;
|
|
|
|
if (en && !we) begin
|
|
case (register_address) inside
|
|
[MSIP_BASE:MSIP_BASE+4*NR_CORES]: begin
|
|
if (CVA6Cfg.XLEN == 32)
|
|
rdata[31:0] = msip_q[$unsigned(address[AddrSelWidth-1+2:2])];
|
|
else
|
|
rdata = msip_q[$unsigned(address[AddrSelWidth-1+2:2])];
|
|
end
|
|
|
|
[MTIMECMP_BASE:MTIMECMP_BASE+8*NR_CORES]: begin
|
|
if (CVA6Cfg.XLEN == 32) begin
|
|
if (address[2:0] == 3'h0)
|
|
rdata[31:0] = mtimecmp_q[$unsigned(address[AddrSelWidth-1+3:3])][31:0];
|
|
else begin
|
|
if (address[2:0] == 3'h4)
|
|
rdata[63:32] = mtimecmp_q[$unsigned(address[AddrSelWidth-1+3:3])][63:32];
|
|
end
|
|
|
|
end else begin
|
|
rdata = mtimecmp_q[$unsigned(address[AddrSelWidth-1+3:3])];
|
|
end
|
|
end
|
|
|
|
[MTIME_BASE:MTIME_BASE+4]: begin
|
|
if (CVA6Cfg.XLEN == 32) begin
|
|
if (address[2:0] == 3'h0)
|
|
rdata[31:0] = mtime_q[31:0];
|
|
else begin
|
|
if (address[2:0] == 3'h4)
|
|
rdata[63:32] = mtime_q[63:32];
|
|
end
|
|
end else begin
|
|
rdata = mtime_q;
|
|
end
|
|
end
|
|
default:;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// -----------------------------
|
|
// IRQ Generation
|
|
// -----------------------------
|
|
// The mtime register has a 64-bit precision on all RV32, RV64, and RV128 systems. Platforms provide a 64-bit
|
|
// memory-mapped machine-mode timer compare register (mtimecmp), which causes a timer interrupt to be posted when the
|
|
// mtime register contains a value greater than or equal (mtime >= mtimecmp) to the value in the mtimecmp register.
|
|
// The interrupt remains posted until it is cleared by writing the mtimecmp register. The interrupt will only be taken
|
|
// if interrupts are enabled and the MTIE bit is set in the mie register.
|
|
always_comb begin : irq_gen
|
|
// check that the mtime cmp register is set to a meaningful value
|
|
for (int unsigned i = 0; i < NR_CORES; i++) begin
|
|
if (mtime_q >= mtimecmp_q[i]) begin
|
|
timer_irq_o[i] = 1'b1;
|
|
end else begin
|
|
timer_irq_o[i] = 1'b0;
|
|
end
|
|
end
|
|
end
|
|
|
|
// -----------------------------
|
|
// RTC time tracking facilities
|
|
// -----------------------------
|
|
// 1. Put the RTC input through a classic two stage edge-triggered synchronizer to filter out any
|
|
// metastability effects (or at least make them unlikely :-))
|
|
clint_sync_wedge i_sync_edge (
|
|
.clk_i,
|
|
.rst_ni,
|
|
.serial_i ( rtc_i ),
|
|
.r_edge_o ( increase_timer ),
|
|
.f_edge_o ( ), // left open
|
|
.serial_o ( ) // left open
|
|
);
|
|
|
|
// Registers
|
|
always_ff @(posedge clk_i or negedge rst_ni) begin
|
|
if (~rst_ni) begin
|
|
mtime_q <= 64'b0;
|
|
mtimecmp_q <= 'b0;
|
|
msip_q <= '0;
|
|
end else begin
|
|
mtime_q <= mtime_n;
|
|
mtimecmp_q <= mtimecmp_n;
|
|
msip_q <= msip_n;
|
|
end
|
|
end
|
|
|
|
assign ipi_o = msip_q;
|
|
|
|
// -------------
|
|
// Assertions
|
|
// --------------
|
|
//pragma translate_off
|
|
`ifndef VERILATOR
|
|
// Static assertion check for appropriate bus width
|
|
initial begin
|
|
assert (AXI_DATA_WIDTH == 64) else $fatal(1, "Timer needs to interface with a 64 bit bus, everything else is not supported");
|
|
end
|
|
`endif
|
|
//pragma translate_on
|
|
|
|
endmodule
|
|
|
|
// TODO(zarubaf): Replace by common-cells 2.0
|
|
module clint_sync_wedge #(
|
|
parameter int unsigned STAGES = 2
|
|
) (
|
|
input logic clk_i,
|
|
input logic rst_ni,
|
|
input logic serial_i,
|
|
output logic r_edge_o,
|
|
output logic f_edge_o,
|
|
output logic serial_o
|
|
);
|
|
logic serial, serial_q;
|
|
|
|
assign serial_o = serial_q;
|
|
assign f_edge_o = (~serial) & serial_q;
|
|
assign r_edge_o = serial & (~serial_q);
|
|
|
|
clint_sync #(
|
|
.STAGES (STAGES)
|
|
) i_sync (
|
|
.clk_i,
|
|
.rst_ni,
|
|
.serial_i,
|
|
.serial_o ( serial )
|
|
);
|
|
|
|
always_ff @(posedge clk_i, negedge rst_ni) begin
|
|
if (!rst_ni) begin
|
|
serial_q <= 1'b0;
|
|
end else begin
|
|
serial_q <= serial;
|
|
end
|
|
end
|
|
endmodule
|
|
|
|
module clint_sync #(
|
|
parameter int unsigned STAGES = 2
|
|
) (
|
|
input logic clk_i,
|
|
input logic rst_ni,
|
|
input logic serial_i,
|
|
output logic serial_o
|
|
);
|
|
|
|
logic [STAGES-1:0] reg_q;
|
|
|
|
always_ff @(posedge clk_i, negedge rst_ni) begin
|
|
if (!rst_ni) begin
|
|
reg_q <= 'h0;
|
|
end else begin
|
|
reg_q <= {reg_q[STAGES-2:0], serial_i};
|
|
end
|
|
end
|
|
|
|
assign serial_o = reg_q[STAGES-1];
|
|
|
|
endmodule
|