diff --git a/examples/simple_system/README.md b/examples/simple_system/README.md index 0b865fce..98a16f3e 100644 --- a/examples/simple_system/README.md +++ b/examples/simple_system/README.md @@ -5,8 +5,8 @@ run stand-alone binaries. It contains: * An Ibex Core * A single memory for instructions and data -* A basic peripheral to write ASCII output to a file and halt simulation from - software +* A basic peripheral to write ASCII output to a file and halt simulation from software +* A basic timer peripheral capable of generating interrupts based on the RISC-V Machine Timer Registers (see RISC-V Privileged Specification, version 1.11, Section 3.1.10) * A software framework to build programs for it ## Prerequisites @@ -118,5 +118,9 @@ Pass `-gui` to use the DVE GUI. |---------------------|--------------------------------------------------------------------------------------------------------| | 0x20000 | ASCII Out, write ASCII characters here that will get output to the log file | | 0x20004 | Simulator Halt, write 1 here to halt the simulation | +| 0x30000 | RISCV timer mtime register | +| 0x30004 | RISCV timer mtimeh register | +| 0x30008 | RISCV timer mtimecmp register | +| 0x3000C | RISCV timer mtimecmph register | | 0x100000 – 0x1FFFFF | 1 MB memory for instruction and data. Execution starts at 0x100080, exception handler base is 0x100000 | diff --git a/examples/simple_system/rtl/ibex_simple_system.sv b/examples/simple_system/rtl/ibex_simple_system.sv index 9a0f322c..4db796ec 100644 --- a/examples/simple_system/rtl/ibex_simple_system.sv +++ b/examples/simple_system/rtl/ibex_simple_system.sv @@ -30,12 +30,16 @@ module ibex_simple_system ( typedef enum { Ram, - SimCtrl + SimCtrl, + Timer } bus_device_e; - localparam NrDevices = 2; + localparam NrDevices = 3; localparam NrHosts = 2; + // interrupts + logic timer_irq; + // host and device signals logic host_req [NrHosts]; logic host_gnt [NrHosts]; @@ -64,6 +68,8 @@ module ibex_simple_system ( assign cfg_device_addr_mask[Ram] = ~32'hFFFFF; // 1 MB assign cfg_device_addr_base[SimCtrl] = 32'h20000; assign cfg_device_addr_mask[SimCtrl] = ~32'h3FF; // 1 kB + assign cfg_device_addr_base[Timer] = 32'h30000; + assign cfg_device_addr_mask[Timer] = ~32'h3FF; // 1 kB `ifdef VERILATOR @@ -152,7 +158,7 @@ module ibex_simple_system ( .data_err_i (host_err[CoreD]), .irq_software_i (1'b0), - .irq_timer_i (1'b0), + .irq_timer_i (timer_irq), .irq_external_i (1'b0), .irq_fast_i (15'b0), .irq_nm_i (1'b0), @@ -193,6 +199,24 @@ module ibex_simple_system ( .rdata_o (device_rdata[SimCtrl]) ); + timer #( + .DataWidth (32), + .AddressWidth (32) + ) u_timer ( + .clk_i (clk_sys), + .rst_ni (rst_sys_n), + + .timer_req_i (device_req[Timer]), + .timer_we_i (device_we[Timer]), + .timer_be_i (device_be[Timer]), + .timer_addr_i (device_addr[Timer]), + .timer_wdata_i (device_wdata[Timer]), + .timer_rvalid_o (device_rvalid[Timer]), + .timer_rdata_o (device_rdata[Timer]), + .timer_err_o (device_err[Timer]), + .timer_intr_o (timer_irq) + ); + // Expose the performance counter array so it's easy to access in // a verilator siumulation logic [63:0] mhpmcounter_vals [32] /*verilator public_flat*/; diff --git a/examples/sw/simple_system/common/crt0.S b/examples/sw/simple_system/common/crt0.S index b3319f1e..8eee92bc 100644 --- a/examples/sw/simple_system/common/crt0.S +++ b/examples/sw/simple_system/common/crt0.S @@ -9,6 +9,9 @@ default_exc_handler: jal x0, simple_exc_handler +timer_handler: + jal x0, simple_timer_handler + reset_handler: /* set all registers to zero */ mv x1, x0 @@ -84,13 +87,15 @@ sleep_loop: .section .vectors, "ax" .option norvc; - // All traps go to the same default_exc_handler. Create a landing pad of nops - // as IRQs can come into different addresses. + // All unimplemented interrupts/exceptions go to the default_exc_handler. .org 0x00 - .rept 31 - nop - .endr + .rept 7 jal x0, default_exc_handler + .endr + jal x0, timer_handler + .rept 23 + jal x0, default_exc_handler + .endr // reset vector .org 0x80 diff --git a/examples/sw/simple_system/common/simple_system_common.c b/examples/sw/simple_system/common/simple_system_common.c index a5729833..8d68803a 100644 --- a/examples/sw/simple_system/common/simple_system_common.c +++ b/examples/sw/simple_system/common/simple_system_common.c @@ -139,3 +139,52 @@ void simple_exc_handler(void) { while(1); } + +volatile uint64_t time_elapsed; +uint64_t time_increment; + +inline static void increment_timecmp(uint64_t time_base) { + uint64_t current_time = timer_read(); + current_time += time_base; + timecmp_update(current_time); +} + +void timer_enable(uint64_t time_base) { + time_elapsed = 0; + time_increment = time_base; + // Set timer values + increment_timecmp(time_base); + // enable timer interrupt + asm volatile("csrs mie, %0\n" : : "r"(0x80)); + // enable global interrupt + asm volatile("csrs mstatus, %0\n" : : "r"(0x8)); +} + +void timer_disable(void) { asm volatile("csrc mie, %0\n" : : "r"(0x80)); } + +uint64_t timer_read(void) { + uint32_t current_timeh; + uint32_t current_time; + // check if time overflowed while reading and try again + do { + current_timeh = DEV_READ(TIMER_BASE + TIMER_MTIMEH, 0); + current_time = DEV_READ(TIMER_BASE + TIMER_MTIME, 0); + } while (current_timeh != DEV_READ(TIMER_BASE + TIMER_MTIMEH, 0)); + uint64_t final_time = ((uint64_t)current_timeh << 32) | current_time; + return final_time; +} + +void timecmp_update(uint64_t new_time) { + DEV_WRITE(TIMER_BASE + TIMER_MTIMECMP, -1); + DEV_WRITE(TIMER_BASE + TIMER_MTIMECMPH, new_time >> 32); + DEV_WRITE(TIMER_BASE + TIMER_MTIMECMP, new_time); +} + +uint64_t get_elapsed_time(void) { return time_elapsed; } + +void simple_timer_handler(void) __attribute__((interrupt)); + +void simple_timer_handler(void) { + increment_timecmp(time_increment); + time_elapsed++; +} diff --git a/examples/sw/simple_system/common/simple_system_common.h b/examples/sw/simple_system/common/simple_system_common.h index 1ab93ef6..57e87784 100644 --- a/examples/sw/simple_system/common/simple_system_common.h +++ b/examples/sw/simple_system/common/simple_system_common.h @@ -55,4 +55,33 @@ void pcount_enable(int enable); */ void pcount_reset(); +/** + * Enables timer interrupt + * + * @param time_base Number of time ticks to count before interrupt + */ +void timer_enable(uint64_t time_base); + +/** + * Returns current mtime value + */ +uint64_t timer_read(void); + +/** + * Set a new timer value + * + * @param new_time New value for time + */ +void timecmp_update(uint64_t new_time); + +/** + * Disables timer interrupt + */ +void timer_disable(void); + +/** + * Returns current global time value + */ +uint64_t get_elapsed_time(void); + #endif diff --git a/examples/sw/simple_system/common/simple_system_regs.h b/examples/sw/simple_system/common/simple_system_regs.h index 8f5a8072..d28d473a 100644 --- a/examples/sw/simple_system/common/simple_system_regs.h +++ b/examples/sw/simple_system/common/simple_system_regs.h @@ -9,4 +9,10 @@ #define SIM_CTRL_OUT 0x0 #define SIM_CTRL_CTRL 0x4 +#define TIMER_BASE 0x30000 +#define TIMER_MTIME 0x0 +#define TIMER_MTIMEH 0x4 +#define TIMER_MTIMECMP 0x8 +#define TIMER_MTIMECMPH 0xC + #endif // SIMPLE_SYSTEM_REGS_H__ diff --git a/examples/sw/simple_system/hello_test/hello_test.c b/examples/sw/simple_system/hello_test/hello_test.c index 2d630dd8..5061faba 100644 --- a/examples/sw/simple_system/hello_test/hello_test.c +++ b/examples/sw/simple_system/hello_test/hello_test.c @@ -17,5 +17,25 @@ int main(int argc, char **argv) { pcount_enable(0); + // Enable periodic timer interrupt + // (the actual timebase is a bit meaningless in simulation) + timer_enable(2000); + + uint64_t last_elapsed_time = get_elapsed_time(); + + while (last_elapsed_time <= 4) { + uint64_t cur_time = get_elapsed_time(); + if (cur_time != last_elapsed_time) { + last_elapsed_time = cur_time; + + if (last_elapsed_time & 1) { + puts("Tick!\n"); + } else { + puts("Tock!\n"); + } + } + asm volatile("wfi"); + } + return 0; } diff --git a/shared/rtl/timer.sv b/shared/rtl/timer.sv new file mode 100644 index 00000000..c4f9fcaf --- /dev/null +++ b/shared/rtl/timer.sv @@ -0,0 +1,151 @@ +// 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 +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, mtimcmph_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'b1; + + // 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 diff --git a/shared/sim_shared.core b/shared/sim_shared.core index e7a224fc..42fea450 100644 --- a/shared/sim_shared.core +++ b/shared/sim_shared.core @@ -6,11 +6,14 @@ name: "lowrisc:ibex:sim_shared" description: "Collection of useful RTL for building simulations" filesets: files_sim_sv: + depend: + - lowrisc:prim:assert files: - ./rtl/prim_clock_gating.sv - ./rtl/ram_1p.sv - ./rtl/bus.sv - ./rtl/sim/simulator_ctrl.sv + - ./rtl/timer.sv file_type: systemVerilogSource targets: