| ////////////////////////////////////////////////////////////////////////////// |
| // SPDX-FileCopyrightText: Syntacore LLC © 2016-2021 |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the 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. |
| // SPDX-License-Identifier: Apache-2.0 |
| // SPDX-FileContributor: Syntacore LLC |
| // ////////////////////////////////////////////////////////////////////////// |
| /// @file <scr1_timer.sv> |
| /// @brief Memory-mapped Timer |
| /// |
| |
| `include "scr1_arch_description.svh" |
| `include "scr1_memif.svh" |
| |
| module scr1_timer ( |
| // Common |
| input logic rst_n, |
| input logic clk, |
| input logic rtc_clk, |
| |
| // Memory interface |
| input logic dmem_req, |
| input logic dmem_cmd, |
| input logic [1:0] dmem_width, |
| input logic [`SCR1_DMEM_AWIDTH-1:0] dmem_addr, |
| input logic [`SCR1_DMEM_DWIDTH-1:0] dmem_wdata, |
| output logic dmem_req_ack, |
| output logic [`SCR1_DMEM_DWIDTH-1:0] dmem_rdata, |
| output logic [1:0] dmem_resp, |
| |
| // Timer interface |
| output logic [63:0] timer_val, |
| output logic timer_irq |
| ); |
| |
| //------------------------------------------------------------------------------- |
| // Local parameters declaration |
| //------------------------------------------------------------------------------- |
| localparam int unsigned SCR1_TIMER_ADDR_WIDTH = 5; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_CONTROL = 5'h0; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_DIVIDER = 5'h4; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_MTIMELO = 5'h8; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_MTIMEHI = 5'hC; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_MTIMECMPLO = 5'h10; |
| localparam logic [SCR1_TIMER_ADDR_WIDTH-1:0] SCR1_TIMER_MTIMECMPHI = 5'h14; |
| |
| localparam int unsigned SCR1_TIMER_CONTROL_EN_OFFSET = 0; |
| localparam int unsigned SCR1_TIMER_CONTROL_CLKSRC_OFFSET = 1; |
| localparam int unsigned SCR1_TIMER_DIVIDER_WIDTH = 10; |
| |
| //------------------------------------------------------------------------------- |
| // Local signals declaration |
| //------------------------------------------------------------------------------- |
| logic [63:0] mtime_reg; |
| logic [63:0] mtime_new; |
| logic [63:0] mtimecmp_reg; |
| logic [63:0] mtimecmp_new; |
| logic timer_en; |
| logic timer_clksrc_rtc; |
| logic [SCR1_TIMER_DIVIDER_WIDTH-1:0] timer_div; |
| |
| logic control_up; |
| logic divider_up; |
| logic mtimelo_up; |
| logic mtimehi_up; |
| logic mtimecmplo_up; |
| logic mtimecmphi_up; |
| |
| logic dmem_req_valid; |
| |
| logic [3:0] rtc_sync; |
| logic rtc_ext_pulse; |
| logic [SCR1_TIMER_DIVIDER_WIDTH-1:0] timeclk_cnt; |
| logic timeclk_cnt_en; |
| logic time_posedge; |
| logic time_cmp_flag; |
| |
| //------------------------------------------------------------------------------- |
| // Registers |
| //------------------------------------------------------------------------------- |
| |
| // CONTROL |
| always_ff @(posedge clk, negedge rst_n) begin |
| if (~rst_n) begin |
| timer_en <= 1'b1; |
| timer_clksrc_rtc <= 1'b0; |
| end else begin |
| if (control_up) begin |
| timer_en <= dmem_wdata[SCR1_TIMER_CONTROL_EN_OFFSET]; |
| timer_clksrc_rtc <= dmem_wdata[SCR1_TIMER_CONTROL_CLKSRC_OFFSET]; |
| end |
| end |
| end |
| |
| // DIVIDER |
| always_ff @(posedge clk, negedge rst_n) begin |
| if (~rst_n) begin |
| timer_div <= '0; |
| end else begin |
| if (divider_up) begin |
| timer_div <= dmem_wdata[SCR1_TIMER_DIVIDER_WIDTH-1:0]; |
| end |
| end |
| end |
| |
| // MTIME |
| assign time_posedge = (timeclk_cnt_en & (timeclk_cnt == 0)); |
| |
| always_comb begin |
| mtime_new = mtime_reg; |
| if (time_posedge) begin |
| mtime_new = mtime_reg + 1'b1; |
| end |
| if (mtimelo_up) begin |
| mtime_new[31:0] = dmem_wdata; |
| end |
| if (mtimehi_up) begin |
| mtime_new[63:32] = dmem_wdata; |
| end |
| end |
| |
| always_ff @(posedge clk, negedge rst_n) begin |
| if (~rst_n) begin |
| mtime_reg <= '0; |
| end else begin |
| if (time_posedge | mtimelo_up | mtimehi_up) begin |
| mtime_reg <= mtime_new; |
| end |
| end |
| end |
| |
| // MTIMECMP |
| always_comb begin |
| mtimecmp_new = mtimecmp_reg; |
| if (mtimecmplo_up) begin |
| mtimecmp_new[31:0] = dmem_wdata; |
| end |
| if (mtimecmphi_up) begin |
| mtimecmp_new[63:32] = dmem_wdata; |
| end |
| end |
| |
| always_ff @(posedge clk, negedge rst_n) begin |
| if (~rst_n) begin |
| mtimecmp_reg <= '0; |
| end else begin |
| if (mtimecmplo_up | mtimecmphi_up) begin |
| mtimecmp_reg <= mtimecmp_new; |
| end |
| end |
| end |
| |
| //------------------------------------------------------------------------------- |
| // Interrupt pending |
| //------------------------------------------------------------------------------- |
| assign time_cmp_flag = (mtime_reg >= ((mtimecmplo_up | mtimecmphi_up) ? mtimecmp_new : mtimecmp_reg)); |
| |
| always_ff @(posedge clk, negedge rst_n) begin |
| if (~rst_n) begin |
| timer_irq <= 1'b0; |
| end else begin |
| if (~timer_irq) begin |
| timer_irq <= time_cmp_flag; |
| end else begin // 1'b1 |
| if (mtimecmplo_up | mtimecmphi_up) begin |
| timer_irq <= time_cmp_flag; |
| end |
| end |
| end |
| end |
| |
| //------------------------------------------------------------------------------- |
| // Timer divider |
| //------------------------------------------------------------------------------- |
| assign timeclk_cnt_en = (~timer_clksrc_rtc ? 1'b1 : rtc_ext_pulse) & timer_en; |
| |
| always_ff @(negedge rst_n, posedge clk) begin |
| if (~rst_n) begin |
| timeclk_cnt <= '0; |
| end else begin |
| case (1'b1) |
| divider_up : timeclk_cnt <= dmem_wdata[SCR1_TIMER_DIVIDER_WIDTH-1:0]; |
| time_posedge : timeclk_cnt <= timer_div; |
| timeclk_cnt_en : timeclk_cnt <= timeclk_cnt - 1'b1; |
| default : begin end |
| endcase |
| end |
| end |
| |
| //------------------------------------------------------------------------------- |
| // RTC synchronization |
| //------------------------------------------------------------------------------- |
| assign rtc_ext_pulse = rtc_sync[3] ^ rtc_sync[2]; |
| |
| always_ff @(negedge rst_n, posedge rtc_clk) begin |
| if (~rst_n) begin |
| rtc_sync[0] <= 1'b0; |
| end else begin |
| if (timer_clksrc_rtc) begin |
| rtc_sync[0] <= ~rtc_sync[0]; |
| end |
| end |
| end |
| |
| always_ff @(negedge rst_n, posedge clk) begin |
| if (~rst_n) begin |
| rtc_sync[3:1] <= '0; |
| end else begin |
| if (timer_clksrc_rtc) begin |
| rtc_sync[3:1] <= rtc_sync[2:0]; |
| end |
| end |
| end |
| |
| //------------------------------------------------------------------------------- |
| // Memory interface |
| //------------------------------------------------------------------------------- |
| assign dmem_req_valid = (dmem_width == SCR1_MEM_WIDTH_WORD) & (~|dmem_addr[1:0]) & |
| (dmem_addr[SCR1_TIMER_ADDR_WIDTH-1:2] <= SCR1_TIMER_MTIMECMPHI[SCR1_TIMER_ADDR_WIDTH-1:2]); |
| |
| assign dmem_req_ack = 1'b1; |
| |
| always_ff @(negedge rst_n, posedge clk) begin |
| if (~rst_n) begin |
| dmem_resp <= SCR1_MEM_RESP_NOTRDY; |
| dmem_rdata <= '0; |
| end else begin |
| if (dmem_req) begin |
| if (dmem_req_valid) begin |
| dmem_resp <= SCR1_MEM_RESP_RDY_OK; |
| if (dmem_cmd == SCR1_MEM_CMD_RD) begin |
| case (dmem_addr[SCR1_TIMER_ADDR_WIDTH-1:0]) |
| SCR1_TIMER_CONTROL : dmem_rdata <= `SCR1_DMEM_DWIDTH'({timer_clksrc_rtc, timer_en}); |
| SCR1_TIMER_DIVIDER : dmem_rdata <= `SCR1_DMEM_DWIDTH'(timer_div); |
| SCR1_TIMER_MTIMELO : dmem_rdata <= mtime_reg[31:0]; |
| SCR1_TIMER_MTIMEHI : dmem_rdata <= mtime_reg[63:32]; |
| SCR1_TIMER_MTIMECMPLO : dmem_rdata <= mtimecmp_reg[31:0]; |
| SCR1_TIMER_MTIMECMPHI : dmem_rdata <= mtimecmp_reg[63:32]; |
| default : begin end |
| endcase |
| end |
| end else begin |
| dmem_resp <= SCR1_MEM_RESP_RDY_ER; |
| end |
| end else begin |
| dmem_resp <= SCR1_MEM_RESP_NOTRDY; |
| dmem_rdata <= '0; |
| end |
| end |
| end |
| |
| always_comb begin |
| control_up = 1'b0; |
| divider_up = 1'b0; |
| mtimelo_up = 1'b0; |
| mtimehi_up = 1'b0; |
| mtimecmplo_up = 1'b0; |
| mtimecmphi_up = 1'b0; |
| if (dmem_req & dmem_req_valid & (dmem_cmd == SCR1_MEM_CMD_WR)) begin |
| case (dmem_addr[SCR1_TIMER_ADDR_WIDTH-1:0]) |
| SCR1_TIMER_CONTROL : control_up = 1'b1; |
| SCR1_TIMER_DIVIDER : divider_up = 1'b1; |
| SCR1_TIMER_MTIMELO : mtimelo_up = 1'b1; |
| SCR1_TIMER_MTIMEHI : mtimehi_up = 1'b1; |
| SCR1_TIMER_MTIMECMPLO : mtimecmplo_up = 1'b1; |
| SCR1_TIMER_MTIMECMPHI : mtimecmphi_up = 1'b1; |
| default : begin end |
| endcase |
| end |
| end |
| |
| //------------------------------------------------------------------------------- |
| // Timer interface |
| //------------------------------------------------------------------------------- |
| assign timer_val = mtime_reg; |
| |
| endmodule : scr1_timer |