// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// Example memory mapped timer

`include "prim_assert.sv"

module timer #(
  // Bus data width (must be 32)
  parameter int unsigned DataWidth    = 32,
  // Bus address width
  parameter int unsigned AddressWidth = 32
) (
  input  logic                    clk_i,
  input  logic                    rst_ni,
  // Bus interface
  input  logic                    timer_req_i,

  input  logic [AddressWidth-1:0] timer_addr_i,
  input  logic                    timer_we_i,
  input  logic [ DataWidth/8-1:0] timer_be_i,
  input  logic [   DataWidth-1:0] timer_wdata_i,
  output logic                    timer_rvalid_o,
  output logic [   DataWidth-1:0] timer_rdata_o,
  output logic                    timer_err_o,
  output logic                    timer_intr_o
);

  // The timers are always 64 bits
  localparam int unsigned TW = 64;
  // Upper bits of address are decoded into timer_req_i
  localparam int unsigned ADDR_OFFSET = 10; // 1kB
  // Register map
  localparam bit [9:0] MTIME_LOW = 0;
  localparam bit [9:0] MTIME_HIGH = 4;
  localparam bit [9:0] MTIMECMP_LOW = 8;
  localparam bit [9:0] MTIMECMP_HIGH = 12;

  logic                 timer_we;
  logic                 mtime_we, mtimeh_we;
  logic                 mtimecmp_we, mtimecmph_we;
  logic [DataWidth-1:0] mtime_wdata, mtimeh_wdata;
  logic [DataWidth-1:0] mtimecmp_wdata, mtimecmph_wdata;
  logic [TW-1:0]        mtime_q, mtime_d, mtime_inc;
  logic [TW-1:0]        mtimecmp_q, mtimecmp_d;
  logic                 interrupt_q, interrupt_d;
  logic                 error_q, error_d;
  logic [DataWidth-1:0] rdata_q, rdata_d;
  logic                 rvalid_q;

  // Global write enable for all registers
  assign timer_we = timer_req_i & timer_we_i;

  // mtime increments every cycle
  assign mtime_inc = mtime_q + 64'd1;

  // Generate write data based on byte strobes
  for (genvar b = 0; b < DataWidth / 8; b++) begin : gen_byte_wdata

    assign mtime_wdata[(b*8)+:8]     = timer_be_i[b] ? timer_wdata_i[b*8+:8] :
                                                       mtime_q[(b*8)+:8];
    assign mtimeh_wdata[(b*8)+:8]    = timer_be_i[b] ? timer_wdata_i[b*8+:8] :
                                                       mtime_q[DataWidth+(b*8)+:8];
    assign mtimecmp_wdata[(b*8)+:8]  = timer_be_i[b] ? timer_wdata_i[b*8+:8] :
                                                       mtimecmp_q[(b*8)+:8];
    assign mtimecmph_wdata[(b*8)+:8] = timer_be_i[b] ? timer_wdata_i[b*8+:8] :
                                                       mtimecmp_q[DataWidth+(b*8)+:8];
  end

  // Generate write enables
  assign mtime_we     = timer_we & (timer_addr_i[ADDR_OFFSET-1:0] == MTIME_LOW);
  assign mtimeh_we    = timer_we & (timer_addr_i[ADDR_OFFSET-1:0] == MTIME_HIGH);
  assign mtimecmp_we  = timer_we & (timer_addr_i[ADDR_OFFSET-1:0] == MTIMECMP_LOW);
  assign mtimecmph_we = timer_we & (timer_addr_i[ADDR_OFFSET-1:0] == MTIMECMP_HIGH);

  // Generate next data
  assign mtime_d    = {(mtimeh_we    ? mtimeh_wdata    : mtime_inc[63:32]),
                       (mtime_we     ? mtime_wdata     : mtime_inc[31:0])};
  assign mtimecmp_d = {(mtimecmph_we ? mtimecmph_wdata : mtimecmp_q[63:32]),
                       (mtimecmp_we  ? mtimecmp_wdata  : mtimecmp_q[31:0])};

  // Generate registers
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (~rst_ni) begin
      mtime_q <= 'b0;
    end else begin
      mtime_q <= mtime_d;
    end
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (~rst_ni) begin
      mtimecmp_q <= 'b0;
    end else if (mtimecmp_we | mtimecmph_we) begin
      mtimecmp_q <= mtimecmp_d;
    end
  end

  // interrupt remains set until mtimecmp is written
  assign interrupt_d  = ((mtime_q >= mtimecmp_q) | interrupt_q) & ~(mtimecmp_we | mtimecmph_we);

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (~rst_ni) begin
      interrupt_q <= 'b0;
    end else begin
      interrupt_q <= interrupt_d;
    end
  end

  assign timer_intr_o = interrupt_q;

  // Read data
  always_comb begin
    rdata_d = 'b0;
    error_d = 1'b0;
    unique case (timer_addr_i[ADDR_OFFSET-1:0])
      MTIME_LOW:     rdata_d = mtime_q[31:0];
      MTIME_HIGH:    rdata_d = mtime_q[63:32];
      MTIMECMP_LOW:  rdata_d = mtimecmp_q[31:0];
      MTIMECMP_HIGH: rdata_d = mtimecmp_q[63:32];
      default: begin
        rdata_d = 'b0;
        // Error if no address matched
        error_d = 1'b1;
      end
    endcase
  end

  // error_q and rdata_q are only valid when rvalid_q is high
  always_ff @(posedge clk_i) begin
    if (timer_req_i) begin
      rdata_q <= rdata_d;
      error_q <= error_d;
    end
  end

  assign timer_rdata_o = rdata_q;

  // Read data is always valid one cycle after a request
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      rvalid_q <= 1'b0;
    end else begin
      rvalid_q <= timer_req_i;
    end
  end

  assign timer_rvalid_o = rvalid_q;
  assign timer_err_o    = error_q;

  // Assertions
  `ASSERT_INIT(param_legal, DataWidth == 32)
endmodule
