diff --git a/hdl/peri/hazard3_riscv_timer.f b/hdl/peri/hazard3_riscv_timer.f index ae9ce66..ef47462 100644 --- a/hdl/peri/hazard3_riscv_timer.f +++ b/hdl/peri/hazard3_riscv_timer.f @@ -1,2 +1 @@ file hazard3_riscv_timer.v -file ../debug/cdc/hazard3_sync_1bit.v diff --git a/hdl/peri/hazard3_riscv_timer.v b/hdl/peri/hazard3_riscv_timer.v index a5ff56c..b9ab01f 100644 --- a/hdl/peri/hazard3_riscv_timer.v +++ b/hdl/peri/hazard3_riscv_timer.v @@ -16,9 +16,19 @@ *********************************************************************/ // 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. +// accessed over 32-bit data bus. +// +// This is written for minimal area on FPGA -- in particular, it uses 64-bit +// serial increment and compare -- so is not the best solution for less +// resource-constrained platforms, because it can't count faster than once +// per 64 cycles, and bus accesses can be delayed for up to 63 timer ticks +// whilst the serial counter rotates to the correct bus alignment. The serial +// operations are also quite energy-intensive. +// +// Tie tick high for a 64-cycle timebase. tick must be free-running, i.e. must +// not be held low indefinitely, because this would also halt the serial +// mtimecmp comparison. To pause the timer due to an external event, assert +// dbg_halt high. To pause from software, write 0 to CTRL.EN. module hazard3_riscv_timer ( input wire clk, @@ -30,17 +40,16 @@ module hazard3_riscv_timer ( input wire [7:0] paddr, input wire [31:0] pwdata, output reg [31:0] prdata, - output wire pready, + output reg pready, output wire pslverr, input wire dbg_halt, - input wire tick_nrz, + + input wire tick, output reg timer_irq ); -wire bus_write = pwrite && psel && penable && pready; - localparam W_ADDR = 8; localparam W_DATA = 32; @@ -50,34 +59,24 @@ 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) +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin ctrl_en <= 1'b1; - else if (bus_write && paddr == ADDR_CTRL) + end else if (bus_write && paddr == ADDR_CTRL) begin ctrl_en <= pwdata[0]; + end +end -wire tick = tick_nrz_prev != tick_nrz_sync; +wire bus_write = pwrite && psel && penable && pready; 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. +// ---------------------------------------------------------------------------- +// mtime and serial increment + +// Increment takes place over the course of 64 ticks. The mtime register is +// constantly rotating to bring a new serial bit into position 0. reg [5:0] serial_ctr; @@ -99,7 +98,6 @@ always @ (posedge clk or negedge rst_n) begin 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 @@ -118,10 +116,20 @@ always @ (posedge clk or negedge rst_n) begin end end +// ---------------------------------------------------------------------------- +// mtimecmp and serial comparison + +// The timer IRQ is only updated every 64 ticks, when we finish a new +// comparison. This is permitted by the RISC-V privileged spec: before +// returning, the IRQ handler should poll mtip until it sees the IRQ +// deassert. + reg [63:0] mtimecmp; reg mtimecmp_borrow; -wire mtimecmp_borrow_next = (!mtime[0] && (mtimecmp[0] || mtimecmp_borrow)) || (mtimecmp[0] && mtimecmp_borrow); +wire mtimecmp_borrow_next = + (!mtime[0] && (mtimecmp[0] || mtimecmp_borrow)) + || (mtimecmp[0] && mtimecmp_borrow); always @ (posedge clk or negedge rst_n) begin if (!rst_n) begin @@ -144,15 +152,21 @@ always @ (posedge clk or negedge rst_n) begin 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[31:0]; pready = serial_ctr == 6'h20; end - default: begin prdata = {W_DATA{1'b0}}; pready = 1'b1; end - endcase -end +// ---------------------------------------------------------------------------- +// Bus read mux + +// Only the lower half of each 64-bit counter register is exposed to the bus, +// using the serial counter to make sure the correct bits are aligned with +// the bus window at the point the bus access finishes. Note pready is only +// valid during the access phase (& is ignored during setup phase). + +always @ (*) 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[31:0]; pready = serial_ctr == 6'h20; end + default: begin prdata = {W_DATA{1'b0}}; pready = 1'b1; end +endcase endmodule