mirror of
https://github.com/openhwgroup/cve2.git
synced 2025-04-22 04:57:25 -04:00
[examples] Add timer example to simple system
Not particularly useful in the current system, but gives an example of how to handle interrupts.
This commit is contained in:
parent
790fab927a
commit
5bb41957ef
9 changed files with 301 additions and 10 deletions
|
@ -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 |
|
||||
|
||||
|
|
|
@ -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*/;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
151
shared/rtl/timer.sv
Normal file
151
shared/rtl/timer.sv
Normal file
|
@ -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
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue