Hazard3/example_soc/soc/peri/hazard3_riscv_timer.v

143 lines
3.4 KiB
Verilog

/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
// Basic implementation of standard 64-bit RISC-V timer with 32-bit APB.
// TICK_IS_NRZ = 1: tick is an NRZ signal that is asynchronous to clk.
// TICK_IS_NRZ = 0: tick is a level-sensitive signal that is synchronous to clk.
module hazard3_riscv_timer #(
parameter TICK_IS_NRZ = 0
) (
input wire clk,
input wire rst_n,
input wire [15:0] paddr,
input wire psel,
input wire penable,
input wire pwrite,
input wire [31:0] pwdata,
output reg [31:0] prdata,
output wire pready,
output wire pslverr,
input wire dbg_halt,
input wire tick,
output reg timer_irq
);
localparam ADDR_CTRL = 16'h0000;
localparam ADDR_MTIME = 16'h0008;
localparam ADDR_MTIMEH = 16'h000c;
localparam ADDR_MTIMECMP = 16'h0010;
localparam ADDR_MTIMECMPH = 16'h0014;
// ----------------------------------------------------------------------------
// Timer tick logic
wire tick_event;
generate
if (TICK_IS_NRZ) begin: edge_detect
wire tick_nrz_sync;
hazard3_sync_1bit tick_sync_u (
.clk (clk),
.rst_n (rst_n),
.i (tick_nrz),
.o (tick_nrz_sync)
);
reg tick_nrz_prev;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
tick_nrz_prev <= 1'b0;
end else begin
tick_nrz_prev <= tick_nrz_sync;
end
end
assign tick_event = tick_nrz_sync ^ tick_nrz_sync_prev;
end else begin: no_edge_detect
assign tick_event = tick;
end
endgenerate
reg ctrl_en;
wire tick_now = tick_event && ctrl_en && !dbg_halt;
// ----------------------------------------------------------------------------
// Counter registers
wire bus_write = pwrite && psel && penable;
wire bus_read = !pwrite && psel && penable;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
ctrl_en <= 1'b1;
end else if (bus_write && paddr == ADDR_CTRL) begin
ctrl_en <= pwdata[0];
end
end
reg [63:0] mtime;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mtime <= 64'h0;
end else begin
if (tick_now)
mtime <= mtime + 1'b1;
if (bus_write && paddr == ADDR_MTIME)
mtime[31:0] <= pwdata;
if (bus_write && paddr == ADDR_MTIMEH)
mtime[63:32] <= pwdata;
end
end
// mtimecmp is stored inverted for minor LUT savings on iCE40
reg [63:0] mtimecmp;
wire [64:0] cmp_diff = {1'b0, mtime} + {1'b0, mtimecmp} + 65'd1;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mtimecmp <= 64'h0;
timer_irq <= 1'b0;
end else begin
if (bus_write && paddr == ADDR_MTIMECMP)
mtimecmp[31:0] <= ~pwdata;
if (bus_write && paddr == ADDR_MTIMECMPH)
mtimecmp[63:32] <= ~pwdata;
timer_irq <= cmp_diff[64];
end
end
always @ (*) begin
case (paddr)
ADDR_CTRL: prdata = {31'h0, ctrl_en};
ADDR_MTIME: prdata = mtime[31:0];
ADDR_MTIMEH: prdata = mtime[63:32];
ADDR_MTIMECMP: prdata = ~mtimecmp[31:0];
ADDR_MTIMECMPH: prdata = ~mtimecmp[63:32];
default: prdata = 32'h0;
endcase
end
assign pready = 1'b1;
assign pslverr = 1'b0;
endmodule
`ifndef YOSYS
`default_nettype none
`endif