Hazard3/hdl/peri/hazard3_riscv_timer.v

159 lines
5.2 KiB
Verilog

/**********************************************************************
* DO WHAT THE FUCK YOU WANT TO AND DON'T BLAME US PUBLIC LICENSE *
* Version 3, April 2008 *
* *
* Copyright (C) 2021 Luke Wren *
* *
* Everyone is permitted to copy and distribute verbatim or modified *
* copies of this license document and accompanying software, and *
* changing either is allowed. *
* *
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION *
* *
* 0. You just DO WHAT THE FUCK YOU WANT TO. *
* 1. We're NOT RESPONSIBLE WHEN IT DOESN'T FUCKING WORK. *
* *
*********************************************************************/
// Implementation of standard RISC-V timer (mtime/mtimeh mtimecmp/mtimecmph)
// accessed over 32-bit data bus. Ticks on both edges of tick_nrz, which is
// provided by some external timebase, and is assumed to be asynchronous to
// clk. Nothing fancy, just a simple implementation of the spec.
module hazard3_riscv_timer (
input wire clk,
input wire rst_n,
input wire psel,
input wire penable,
input wire pwrite,
input wire [7:0] paddr,
input wire [31:0] pwdata,
output reg [31:0] prdata,
output wire pready,
output wire pslverr,
input wire dbg_halt,
input wire tick_nrz,
output reg timer_irq
);
wire bus_write = pwrite && psel && penable && pready;
localparam W_ADDR = 8;
localparam W_DATA = 32;
localparam ADDR_CTRL = 8'h00;
localparam ADDR_MTIME = 8'h08;
localparam ADDR_MTIMEH = 8'h0c;
localparam ADDR_MTIMECMP = 8'h10;
localparam ADDR_MTIMECMPH = 8'h14;
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)
if (!rst_n)
tick_nrz_prev <= 1'b0;
else
tick_nrz_prev <= tick_nrz_sync;
reg ctrl_en;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
ctrl_en <= 1'b1;
else if (bus_write && paddr == ADDR_CTRL)
ctrl_en <= pwdata[0];
wire tick = tick_nrz_prev != tick_nrz_sync;
wire tick_and_increment = ctrl_en && !dbg_halt && tick;
// The 64-bit TIME and TIMECMP registers are processed serially, over the
// course of 64 cycles.
reg [5:0] serial_ctr;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
serial_ctr <= 6'h00;
end else if (tick) begin
serial_ctr <= serial_ctr + 1'b1;
end
end
reg [63:0] mtime;
reg mtime_carry;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mtime <= 64'h0;
mtime_carry <= 1'b1;
end else begin
if (tick) begin
if (tick_and_increment) begin
// Serially increment mtime
{mtime_carry, mtime[63]} <= mtime_carry + mtime[0];
mtime[62:0] <= mtime[63:1];
end else begin
// Still keep rotating the register, so writes can take place,
// and so we can continuously compare with mtimecmp.
mtime <= {mtime[0], mtime[63:1]};
end
// Preload carry for increment
if (serial_ctr == 6'h3f)
mtime_carry <= 1'b1;
end
// Only the lower half is written; pready is driven so that the write
// occurs at the correct time and hence bit-alignment.
if (bus_write && (paddr == ADDR_MTIME || paddr == ADDR_MTIMEH))
mtime[31:0] <= pwdata;
end
end
reg [63:0] mtimecmp;
reg mtimecmp_borrow;
wire mtimecmp_borrow_next = (!mtimecmp[0] && (mtime[0] || mtimecmp_borrow)) || (mtime[0] && mtimecmp_borrow);
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mtimecmp <= 64'h0;
mtimecmp_borrow <= 1'b0;
timer_irq <= 1'b0;
end else begin
// Serially subtract mtime from mtimecmp. If there is no borrow from
// bit 63 (i.e. if mtimecmp was greater or equal) then assert IRQ.
if (tick) begin
mtimecmp_borrow <= mtimecmp_borrow_next;
mtimecmp <= {mtimecmp[0], mtimecmp[63:1]};
if (serial_ctr == 6'h3f) begin
mtimecmp_borrow <= 1'b0;
timer_irq <= !mtimecmp_borrow_next;
end
end
if (bus_write && (paddr == ADDR_MTIMECMP || paddr == ADDR_MTIMECMPH))
mtimecmp[31:0] <= pwdata;
end
end
always @ (*) begin
case (paddr)
ADDR_CTRL: begin prdata = {{W_DATA-1{1'b0}}, ctrl_en}; pready = 1'b1; end
ADDR_MTIME: begin prdata = mtime[31:0]; pready = serial_ctr == 6'h00; end
ADDR_MTIMEH: begin prdata = mtime[31:0]; pready = serial_ctr == 6'h20; end
ADDR_MTIMECMP: begin prdata = mtimecmp[31:0]; pready = serial_ctr == 6'h00; end
ADDR_MTIMECMPH: begin prdata = mtimecmp[63:32]; pready = serial_ctr == 6'h20; end
default: begin prdata = {W_DATA{1'b0}}; pready = 1'b1; end
endcase
end
endmodule