RTC Module from Opencores
diff --git a/verilog/rtl/rtc/hexmap.v b/verilog/rtl/rtc/hexmap.v new file mode 100644 index 0000000..643ea02 --- /dev/null +++ b/verilog/rtl/rtc/hexmap.v
@@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Filename: hexmap.v +// +// Project: A Real--time Clock Core +// +// Purpose: Converts a 4'bit hexadecimal value to the seven bits needed +// by a seven segment display, specifying which bits are on and +// which are off. +// +// The display I am working with, however, requires a separate +// controller. This file only provides part of the input for that +// controller. That controller deals with turning on each part +// of the display in a rotating fashion, since the hardware I have +// cannot display more than one character at a time. So, +// buyer beware--this is not a complete seven segment display +// solution. +// +// +// The outputs of this routine are numbered as follows: +// o_map[7] turns on the bar at the top of the display +// o_map[6] turns on the top of the '1' +// o_map[5] turns on the bottom of a '1' +// o_map[4] turns on the bar at the bottom of the display +// o_map[3] turns on the vertical bar at the bottom left +// o_map[2] turns on the vertical bar at the top left, and +// o_map[1] turns on the bar in the middle of the display. +// The dash if you will. +// Bit zero, from elsewhere, would be the decimal point. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Tecnology, LLC +// +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015, Gisselquist Technology, LLC +// +// This program is free software (firmware): you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. (It's in the $(ROOT)/doc directory. Run make with no +// target there if the PDF file isn't present.) If not, see +// <http://www.gnu.org/licenses/> for a copy. +// +// License: GPL, v3, as defined and found on www.gnu.org, +// http://www.gnu.org/licenses/gpl.html +// +// +/////////////////////////////////////////////////////////////////////////// +module hexmap(i_clk, i_hex, o_map); + input i_clk; + input [3:0] i_hex; + output reg [7:1] o_map; + + always @(posedge i_clk) + case(i_hex) + 4'h0: o_map <= { 7'b1111110 }; + 4'h1: o_map <= { 7'b0110000 }; + 4'h2: o_map <= { 7'b1101101 }; + 4'h3: o_map <= { 7'b1111001 }; + 4'h4: o_map <= { 7'b0110011 }; + 4'h5: o_map <= { 7'b1011011 }; + 4'h6: o_map <= { 7'b1011111 }; + 4'h7: o_map <= { 7'b1110000 }; + 4'h8: o_map <= { 7'b1111111 }; + 4'h9: o_map <= { 7'b1111011 }; + 4'ha: o_map <= { 7'b1110111 }; + 4'hb: o_map <= { 7'b0011111 }; // b + 4'hc: o_map <= { 7'b1001110 }; + 4'hd: o_map <= { 7'b0111101 }; // d + 4'he: o_map <= { 7'b1001111 }; + 4'hf: o_map <= { 7'b1000111 }; + endcase +endmodule
diff --git a/verilog/rtl/rtc/rtcclock.v b/verilog/rtl/rtc/rtcclock.v new file mode 100644 index 0000000..2099964 --- /dev/null +++ b/verilog/rtl/rtc/rtcclock.v
@@ -0,0 +1,504 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Filename: rtcclock.v +// +// Project: A Wishbone Controlled Real--time Clock Core +// +// Purpose: Implement a real time clock, including alarm, count--down +// timer, stopwatch, variable time frequency, and more. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015, Gisselquist Technology, LLC +// +// This program is free software (firmware): you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. (It's in the $(ROOT)/doc directory. Run make with no +// target there if the PDF file isn't present.) If not, see +// <http://www.gnu.org/licenses/> for a copy. +// +// License: GPL, v3, as defined and found on www.gnu.org, +// http://www.gnu.org/licenses/gpl.html +// +// +/////////////////////////////////////////////////////////////////////////// +module rtcclock(i_clk, + // Wishbone interface + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, + // o_wb_ack, o_wb_stb, o_wb_data, // no reads here + // // Button inputs + // i_btn, + // Output registers + o_data, // multiplexed based upon i_wb_addr + // Output controls + o_sseg, o_led, o_interrupt, + // A once-per-day strobe on the last clock of the day + o_ppd, + // Time setting hack(s) + i_hack); + parameter DEFAULT_SPEED = 32'd2814750; //2af31e = 2^48 / 100e6 MHz + input i_clk; + input i_wb_cyc, i_wb_stb, i_wb_we; + input [2:0] i_wb_addr; + input [31:0] i_wb_data; + // input i_btn; + output reg [31:0] o_data; + output reg [31:0] o_sseg; + output wire [15:0] o_led; + output wire o_interrupt, o_ppd; + input i_hack; + + reg [31:0] stopwatch, ckspeed; + reg [25:0] clock, timer; + + wire ck_sel, tm_sel, sw_sel, sp_sel, al_sel; + assign ck_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b000)); + assign tm_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b001)); + assign sw_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b010)); + assign al_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b011)); + assign sp_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b100)); + + reg [39:0] ck_counter; + reg ck_carry; + always @(posedge i_clk) + { ck_carry, ck_counter } <= ck_counter + { 8'h00, ckspeed }; + + wire ck_pps; + reg ck_prepps, ck_ppm, ck_pph, ck_ppd; + reg [7:0] ck_sub; + initial clock = 26'h000000; + assign ck_pps = (ck_carry)&&(ck_prepps); + always @(posedge i_clk) + begin + if (ck_carry) + ck_sub <= ck_sub + 8'h1; + ck_prepps <= (ck_sub == 8'hff); + + if (ck_pps) + begin // advance the seconds + if (clock[3:0] >= 4'h9) + clock[3:0] <= 4'h0; + else + clock[3:0] <= clock[3:0] + 4'h1; + if (clock[7:0] >= 8'h59) + clock[7:4] <= 4'h0; + else if (clock[3:0] >= 4'h9) + clock[7:4] <= clock[7:4] + 4'h1; + end + ck_ppm <= (clock[7:0] == 8'h59); + + if ((ck_pps)&&(ck_ppm)) + begin // advance the minutes + if (clock[11:8] >= 4'h9) + clock[11:8] <= 4'h0; + else + clock[11:8] <= clock[11:8] + 4'h1; + if (clock[15:8] >= 8'h59) + clock[15:12] <= 4'h0; + else if (clock[11:8] >= 4'h9) + clock[15:12] <= clock[15:12] + 4'h1; + end + ck_pph <= (clock[15:0] == 16'h5959); + + if ((ck_pps)&&(ck_pph)) + begin // advance the hours + if (clock[21:16] >= 6'h23) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= 2'h0; + end else if (clock[19:16] >= 4'h9) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= clock[21:20] + 2'h1; + end else begin + clock[19:16] <= clock[19:16] + 4'h1; + end + end + ck_ppd <= (clock[21:0] == 22'h235959); + + + if ((ck_sel)&&(i_wb_we)) + begin + if (8'hff != i_wb_data[7:0]) + begin + clock[7:0] <= i_wb_data[7:0]; + ck_ppm <= (i_wb_data[7:0] == 8'h59); + end + if (8'hff != i_wb_data[15:8]) + begin + clock[15:8] <= i_wb_data[15:8]; + ck_pph <= (i_wb_data[15:8] == 8'h59); + end + if (6'h3f != i_wb_data[21:16]) + clock[21:16] <= i_wb_data[21:16]; + clock[25:22] <= i_wb_data[25:22]; + if (8'h00 == i_wb_data[7:0]) + ck_sub <= 8'h00; + end + end + + // Clock updates take several clocks, so let's make sure we + // are only looking at a valid clock value before testing it. + reg [21:0] ck_last_clock; + always @(posedge i_clk) + ck_last_clock <= clock[21:0]; + + + reg tm_pps, tm_ppm, tm_int; + wire tm_stopped, tm_running, tm_alarm; + assign tm_stopped = ~timer[24]; + assign tm_running = timer[24]; + assign tm_alarm = timer[25]; + reg [23:0] tm_start; + reg [7:0] tm_sub; + initial tm_start = 24'h00; + initial timer = 26'h00; + initial tm_int = 1'b0; + initial tm_pps = 1'b0; + always @(posedge i_clk) + begin + if (ck_carry) + begin + tm_sub <= tm_sub + 8'h1; + tm_pps <= (tm_sub == 8'hff); + end else + tm_pps <= 1'b0; + + if ((~tm_alarm)&&(tm_running)&&(tm_pps)) + begin // If we are running ... + timer[25] <= 1'b0; + if (timer[23:0] == 24'h00) + timer[25] <= 1'b1; + else if (timer[3:0] != 4'h0) + timer[3:0] <= timer[3:0]-4'h1; + else begin // last digit is a zero + timer[3:0] <= 4'h9; + if (timer[7:4] != 4'h0) + timer[7:4] <= timer[7:4]-4'h1; + else begin // last two digits are zero + timer[7:4] <= 4'h5; + if (timer[11:8] != 4'h0) + timer[11:8] <= timer[11:8]-4'h1; + else begin // last three digits are zero + timer[11:8] <= 4'h9; + if (timer[15:12] != 4'h0) + timer[15:12] <= timer[15:12]-4'h1; + else begin + timer[15:12] <= 4'h5; + if (timer[19:16] != 4'h0) + timer[19:16] <= timer[19:16]-4'h1; + else begin + // + timer[19:16] <= 4'h9; + timer[23:20] <= timer[23:20]-4'h1; + end + end + end + end + end + end + + if((~tm_alarm)&&(tm_running)) + begin + timer[25] <= (timer[23:0] == 24'h00); + tm_int <= (timer[23:0] == 24'h00); + end else tm_int <= 1'b0; + if (tm_alarm) + timer[24] <= 1'b0; + + if ((tm_sel)&&(i_wb_we)&&(tm_running)) // Writes while running + // Only allowed to stop the timer, nothing more + timer[24] <= i_wb_data[24]; + else if ((tm_sel)&&(i_wb_we)&&(tm_stopped)) // Writes while off + begin + timer[24] <= i_wb_data[24]; + if ((timer[24])||(i_wb_data[24])) + timer[25] <= 1'b0; + if (i_wb_data[23:0] != 24'h0000) + begin + timer[23:0] <= i_wb_data[23:0]; + tm_start <= i_wb_data[23:0]; + tm_sub <= 8'h00; + end else if (timer[23:0] == 24'h00) + begin // Resetting timer to last valid timer start val + timer[23:0] <= tm_start; + tm_sub <= 8'h00; + end + // Any write clears the alarm + timer[25] <= 1'b0; + end + end + + // + // Stopwatch functionality + // + // Setting bit '0' starts the stop watch, clearing it stops it. + // Writing to the register with bit '1' high will clear the stopwatch, + // and return it to zero provided that the stopwatch is stopped either + // before or after the write. Hence, writing a '2' to the device + // will always stop and clear it, whereas writing a '3' to the device + // will only clear it if it was already stopped. + reg sw_pps, sw_ppm, sw_pph; + reg [7:0] sw_sub; + wire sw_running; + assign sw_running = stopwatch[0]; + initial stopwatch = 32'h00000; + always @(posedge i_clk) + begin + sw_pps <= 1'b0; + if (sw_running) + begin + if (ck_carry) + begin + sw_sub <= sw_sub + 8'h1; + sw_pps <= (sw_sub == 8'hff); + end + end + + stopwatch[7:1] <= sw_sub[7:1]; + + if (sw_pps) + begin // Second hand + if (stopwatch[11:8] >= 4'h9) + stopwatch[11:8] <= 4'h0; + else + stopwatch[11:8] <= stopwatch[11:8] + 4'h1; + + if (stopwatch[15:8] >= 8'h59) + stopwatch[15:12] <= 4'h0; + else if (stopwatch[11:8] >= 4'h9) + stopwatch[15:12] <= stopwatch[15:12] + 4'h1; + sw_ppm <= (stopwatch[15:8] == 8'h59); + end else sw_ppm <= 1'b0; + + if (sw_ppm) + begin // Minutes + if (stopwatch[19:16] >= 4'h9) + stopwatch[19:16] <= 4'h0; + else + stopwatch[19:16] <= stopwatch[19:16]+4'h1; + + if (stopwatch[23:16] >= 8'h59) + stopwatch[23:20] <= 4'h0; + else if (stopwatch[19:16] >= 4'h9) + stopwatch[23:20] <= stopwatch[23:20]+4'h1; + sw_pph <= (stopwatch[23:16] == 8'h59); + end else sw_pph <= 1'b0; + + if (sw_pph) + begin // And hours + if (stopwatch[27:24] >= 4'h9) + stopwatch[27:24] <= 4'h0; + else + stopwatch[27:24] <= stopwatch[27:24]+4'h1; + + if((stopwatch[27:24] >= 4'h9)&&(stopwatch[31:28] < 4'hf)) + stopwatch[31:28] <= stopwatch[27:24]+4'h1; + end + + if ((sw_sel)&&(i_wb_we)) + begin + stopwatch[0] <= i_wb_data[0]; + if((i_wb_data[1])&&((~stopwatch[0])||(~i_wb_data[0]))) + begin + stopwatch[31:1] <= 31'h00; + sw_sub <= 8'h00; + sw_pps <= 1'b0; + sw_ppm <= 1'b0; + sw_pph <= 1'b0; + end + end + end + + // + // The alarm code + // + // Set the alarm register to the time you wish the board to "alarm". + // The "alarm" will take place once per day at that time. At that + // time, the RTC code will generate a clock interrupt, and the CPU/host + // can come and see that the alarm tripped. + // + // + reg [21:0] alarm_time; + reg al_int, // The alarm interrupt line + al_enabled, // Whether the alarm is enabled + al_tripped; // Whether the alarm has tripped + initial al_enabled= 1'b0; + initial al_tripped= 1'b0; + always @(posedge i_clk) + begin + if ((al_sel)&&(i_wb_we)) + begin + // Only adjust the alarm hours if the requested hours + // are valid. This allows writes to the register, + // without a prior read, to leave these configuration + // bits alone. + if (i_wb_data[21:16] != 6'h3f) + alarm_time[21:16] <= i_wb_data[21:16]; + // Here's the same thing for the minutes: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[15:8] != 8'hff) + alarm_time[15:8] <= i_wb_data[15:8]; + // Here's the same thing for the seconds: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[7:0] != 8'hff) + alarm_time[7:0] <= i_wb_data[7:0]; + al_enabled <= i_wb_data[24]; + // Reset the alarm if a '1' is written to the tripped + // register, or if the alarm is disabled. + if ((i_wb_data[25])||(~i_wb_data[24])) + al_tripped <= 1'b0; + end + + al_int <= 1'b0; + if ((ck_last_clock != alarm_time)&&(clock[21:0] == alarm_time) + &&(al_enabled)) + begin + al_tripped <= 1'b1; + al_int <= 1'b1; + end + end + + // + // The ckspeed register is equal to 2^48 divded by the number of + // clock ticks you expect per second. Adjust high for a slower + // clock, lower for a faster clock. In this fashion, a single + // real time clock RTL file can handle tracking the clock in any + // device. Further, because this is only the lower 32 bits of a + // 48 bit counter per seconds, the clock jitter is kept below + // 1 part in 65 thousand. + // + initial ckspeed = DEFAULT_SPEED; + // In the case of verilator, comment the above and uncomment the line + // below. The clock constant below is "close" to simulation time, + // meaning that my verilator simulation is running about 300x slower + // than board time. + // initial ckspeed = 32'd786432000; + always @(posedge i_clk) + if ((sp_sel)&&(i_wb_we)) + ckspeed <= i_wb_data; + + // + // If you want very fine precision control over your clock, you need + // to be able to transfer time from one location to another. This + // is the beginning of that means: by setting a wire, i_hack, high + // on a particular input, you can then read (later) what the clock + // time was on that input. + // + // What's missing from this high precision adjustment mechanism is a + // means of actually adjusting this time based upon the time + // difference you measure here between the hack time and some time + // on another clock, but we'll get there. + // + reg r_hack_carry; + reg [29:0] hack_time; + reg [39:0] hack_counter; + initial hack_time = 30'h0000; + initial hack_counter = 40'h0000; + always @(posedge i_clk) + if (i_hack) + begin + hack_time <= { clock[21:0], ck_sub }; + hack_counter <= ck_counter; + r_hack_carry <= ck_carry; + // if ck_carry is set, the clock register is in the + // middle of a two clock update. In that case .... + end else if (r_hack_carry) + begin // update again on the next clock to get the correct + // hack time. + hack_time <= { clock[21:0], ck_sub }; + r_hack_carry <= 1'b0; + end + + reg [15:0] h_sseg; + reg [3:1] dmask; + always @(posedge i_clk) + case(clock[25:24]) + 2'h1: begin h_sseg <= timer[15:0]; + if (tm_alarm) dmask <= 3'h7; + else begin + dmask[3] <= (12'h000 != timer[23:12]); // timer[15:12] + dmask[2] <= (16'h000 != timer[23: 8]); // timer[11: 8] + dmask[1] <= (20'h000 != timer[23: 4]); // timer[ 7: 4] + // dmask[0] <= 1'b1; // Always on + end end + 2'h2: begin h_sseg <= stopwatch[19:4]; + dmask[3] <= (12'h00 != stopwatch[27:16]); + dmask[2] <= (16'h000 != stopwatch[27:12]); + dmask[1] <= 1'b1; // Always on, stopwatch[11:8] + // dmask[0] <= 1'b1; // Always on, stopwatch[7:4] + end + 2'h3: begin h_sseg <= ck_last_clock[15:0]; + dmask[3:1] <= 3'h7; + end + default: begin // 4'h0 + h_sseg <= { 2'b00, ck_last_clock[21:8] }; + dmask[2:1] <= 2'b11; + dmask[3] <= (2'b00 != ck_last_clock[21:20]); + end + endcase + + wire [31:0] w_sseg; + assign w_sseg[ 0] = (~ck_sub[7]); + assign w_sseg[ 8] = (clock[25:24] == 2'h2); + assign w_sseg[16] = ((clock[25:24] == 2'h0)&&(~ck_sub[7]))||(clock[25:24] == 2'h3); + assign w_sseg[24] = 1'b0; + hexmap ha(i_clk, h_sseg[ 3: 0], w_sseg[ 7: 1]); + hexmap hb(i_clk, h_sseg[ 7: 4], w_sseg[15: 9]); + hexmap hc(i_clk, h_sseg[11: 8], w_sseg[23:17]); + hexmap hd(i_clk, h_sseg[15:12], w_sseg[31:25]); + + always @(posedge i_clk) + if ((tm_alarm || al_tripped)&&(ck_sub[7])) + o_sseg <= 32'h0000; + else + o_sseg <= { + (dmask[3])?w_sseg[31:24]:8'h00, + (dmask[2])?w_sseg[23:16]:8'h00, + (dmask[1])?w_sseg[15: 8]:8'h00, + w_sseg[ 7: 0] }; + + reg [17:0] ledreg; + always @(posedge i_clk) + if ((ck_pps)&&(ck_ppm)) + ledreg <= 18'h00; + else if (ck_carry) + ledreg <= ledreg + 18'h11; + assign o_led = (tm_alarm||al_tripped)?{ (16){ck_sub[7]}}: + { ledreg[17:10], + ledreg[10], ledreg[11], ledreg[12], ledreg[13], + ledreg[14], ledreg[15], ledreg[16], ledreg[17] }; + + assign o_interrupt = tm_int || al_int; + + // A once-per day strobe, on the last second of the day so that the + // the next clock is the first clock of the day. This is useful for + // connecting this module to a year/month/date date/calendar module. + assign o_ppd = (ck_ppd)&&(ck_pps); + + always @(posedge i_clk) + case(i_wb_addr[2:0]) + 3'b000: o_data <= { 6'h00, clock[25:22], ck_last_clock }; + 3'b001: o_data <= { 6'h00, timer }; + 3'b010: o_data <= stopwatch; + 3'b011: o_data <= { 6'h00, al_tripped, al_enabled, 2'b00, alarm_time }; + 3'b100: o_data <= ckspeed; + 3'b101: o_data <= { 2'b00, hack_time }; + 3'b110: o_data <= hack_counter[39:8]; + 3'b111: o_data <= { hack_counter[7:0], 24'h00 }; + endcase + +endmodule
diff --git a/verilog/rtl/rtc/rtcdate.v b/verilog/rtl/rtc/rtcdate.v new file mode 100644 index 0000000..f6981c0 --- /dev/null +++ b/verilog/rtl/rtc/rtcdate.v
@@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Filename: rtcdate.v +// +// Project: A Wishbone Controlled Real--time Clock Core +// +// Purpose: +// This core provides a real-time date function that can be coupled with +// a real-time clock. The date provided is in Binary Coded Decimal (bcd) +// form, and available for reading and writing over the Wishbone Bus. +// +// WARNING: Race conditions exist when updating the date across the Wishbone +// bus at or near midnight. (This should be obvious, but it bears +// stating.) Specifically, if the update command shows up at the same +// clock as the ppd clock, then the ppd clock will be ignored and the +// new date will be the date of the day following midnight. However, +// if the update command shows up one clock before the ppd, then the date +// may be updated, but may have problems dealing with the last day of the +// month or year. To avoid race conditions, update the date sometime +// after the stroke of midnight and before 5 clocks before the next +// midnight. If you are concerned that you might hit a race condition, +// just read the clock again (5+ clocks later) to make certain you set +// it correctly. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015, Gisselquist Technology, LLC +// +// This program is free software (firmware): you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. (It's in the $(ROOT)/doc directory. Run make with no +// target there if the PDF file isn't present.) If not, see +// <http://www.gnu.org/licenses/> for a copy. +// +// License: GPL, v3, as defined and found on www.gnu.org, +// http://www.gnu.org/licenses/gpl.html +// +// +/////////////////////////////////////////////////////////////////////////// +module rtcdate(i_clk, i_ppd, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_data, + o_wb_ack, o_wb_stall, o_wb_data); + input i_clk; + // A one part per day signal, i.e. basically a clock enable line that + // controls when the beginning of the day happens. This line should + // be high on the very last second of any day in order for the rtcdate + // module to always have the right date. + input i_ppd; + // Wishbone inputs + input i_wb_cyc, i_wb_stb, i_wb_we; + input [31:0] i_wb_data; + // Wishbone outputs + output reg o_wb_ack; + output wire o_wb_stall; + output wire [31:0] o_wb_data; + + + reg [5:0] r_day; + reg [4:0] r_mon; + reg [13:0] r_year; + + reg last_day_of_month, last_day_of_year, is_leap_year; + always @(posedge i_clk) + last_day_of_year <= (last_day_of_month) && (r_mon == 5'h12); + always @(posedge i_clk) + begin + case(r_mon) + 5'h01: last_day_of_month <= (r_day >= 6'h31); // Jan + 5'h02: last_day_of_month <= (r_day >= 6'h29) + ||((~is_leap_year)&&(r_day == 6'h28)); + 5'h03: last_day_of_month <= (r_day >= 6'h31); // March + 5'h04: last_day_of_month <= (r_day >= 6'h30); // April + 5'h05: last_day_of_month <= (r_day >= 6'h31); // May + 5'h06: last_day_of_month <= (r_day >= 6'h30); // June + 5'h07: last_day_of_month <= (r_day >= 6'h31); // July + 5'h08: last_day_of_month <= (r_day >= 6'h31); // August + 5'h09: last_day_of_month <= (r_day >= 6'h30); // Sept + 5'h10: last_day_of_month <= (r_day >= 6'h31); // October + 5'h11: last_day_of_month <= (r_day >= 6'h30); // November + 5'h12: last_day_of_month <= (r_day >= 6'h31); // December + default: last_day_of_month <= 1'b0; + endcase + end + + reg year_divisible_by_four, century_year, four_century_year; + always @(posedge i_clk) + year_divisible_by_four<= ((~r_year[0])&&(r_year[4]==r_year[1])); + always @(posedge i_clk) + century_year <= (r_year[7:0] == 8'h00); + always @(posedge i_clk) + four_century_year <= ((~r_year[8])&&((r_year[12]==r_year[9]))); + always @(posedge i_clk) + is_leap_year <= (year_divisible_by_four)&&((~century_year) + ||((century_year)&&(four_century_year))); + + + // Adjust the day of month + initial r_day = 6'h01; + always @(posedge i_clk) + begin + if ((r_day == 0)||(r_day > 6'h31)||(r_day[3:0] > 4'h9)) + r_day <= 6'h01; + else if ((i_ppd)&&(last_day_of_month)) + r_day <= 6'h01; + else if ((i_ppd)&&(r_day[3:0] != 4'h9)) + r_day[3:0] <= r_day[3:0] + 4'h1; + else if (i_ppd) + begin + r_day[3:0] <= 4'h0; + r_day[5:4] <= r_day[5:4] + 2'h1; + end + + if ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_we)&&(i_wb_data[7:0]!=8'hff)) + r_day <= i_wb_data[5:0]; + end + + // Adjust the month of the year + initial r_mon = 5'h01; + always @(posedge i_clk) + begin + if ((r_mon == 0)||(r_mon > 5'h12)||(r_mon[3:0] > 4'h9)) + r_mon <= 5'h01; + else if ((i_ppd)&&(last_day_of_year)) + r_mon <= 5'h01; + else if ((i_ppd)&&(last_day_of_month)&&(r_mon[3:0] != 4'h9)) + r_mon[3:0] <= r_mon[3:0] + 4'h1; + else if ((i_ppd)&&(last_day_of_month)) + begin + r_mon[3:0] <= 4'h0; + r_mon[4] <= 1; + end + + if ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_we)&&(i_wb_data[15:8]!=8'hff)) + r_mon <= i_wb_data[12:8]; + end + + // Adjust the year + initial r_year = 14'h2000; + always @(posedge i_clk) + begin + // Deal with any out of bounds conditions + if (r_year[3:0] > 4'h9) + r_year[3:0] <= 4'h0; + if (r_year[7:4] > 4'h9) + r_year[7:4] <= 4'h0; + if (r_year[11:8] > 4'h9) + r_year[11:8] <= 4'h0; + if ((i_ppd)&&(last_day_of_year)) + begin + if (r_year[3:0] != 4'h9) + r_year[3:0] <= r_year[3:0] + 4'h1; + else begin + r_year[3:0] <= 4'h0; + if (r_year[7:4] != 4'h9) + r_year[7:4] <= r_year[7:4] + 4'h1; + else begin + r_year[7:4] <= 4'h0; + if (r_year[11:8] != 4'h9) + r_year[11:8] <= r_year[11:8]+4'h1; + else begin + r_year[11:8] <= 4'h0; + r_year[13:12] <= r_year[13:12]+2'h1; + end + end + end + end + + if ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_we)&&(i_wb_data[31:16]!=16'hffff)) + r_year <= i_wb_data[29:16]; + end + + always @(posedge i_clk) + o_wb_ack <= ((i_wb_cyc)&&(i_wb_stb)); + assign o_wb_stall = 1'b0; + assign o_wb_data = { 2'h0, r_year, 3'h0, r_mon, 2'h0, r_day }; +endmodule
diff --git a/verilog/rtl/rtc/rtcgps.v b/verilog/rtl/rtc/rtcgps.v new file mode 100644 index 0000000..8a401cc --- /dev/null +++ b/verilog/rtl/rtc/rtcgps.v
@@ -0,0 +1,498 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Filename: rtcgps.v +// +// Project: A Wishbone Controlled Real--time Clock Core, w/ GPS synch +// +// Purpose: Implement a real time clock, including alarm, count--down +// timer, stopwatch, variable time frequency, and more. +// +// This particular version has hooks for a GPS 1PPS, as well as a +// finely tracked clock speed output, to allow for fine clock precision +// and good freewheeling even if/when GPS is lost. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015, Gisselquist Technology, LLC +// +// This program is free software (firmware): you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. (It's in the $(ROOT)/doc directory. Run make with no +// target there if the PDF file isn't present.) If not, see +// <http://www.gnu.org/licenses/> for a copy. +// +// License: GPL, v3, as defined and found on www.gnu.org, +// http://www.gnu.org/licenses/gpl.html +// +// +/////////////////////////////////////////////////////////////////////////// +module rtcgps(i_clk, + // Wishbone interface + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, + // o_wb_ack, o_wb_stb, o_wb_data, // no reads here + // // Button inputs + // i_btn, + // Output registers + o_data, // multiplexed based upon i_wb_addr + // Output controls + o_sseg, o_led, o_interrupt, + // A once-per-day strobe on the last clock of the day + o_ppd, + // GPS interface + i_gps_valid, i_gps_pps, i_gps_ckspeed, + // Our personal timing, for debug purposes + o_rtc_pps); + parameter DEFAULT_SPEED = 32'd2814750; //2af31e = 2^48 / 100e6 MHz + input i_clk; + input i_wb_cyc, i_wb_stb, i_wb_we; + input [1:0] i_wb_addr; + input [31:0] i_wb_data; + // input i_btn; + output reg [31:0] o_data; + output reg [31:0] o_sseg; + output wire [15:0] o_led; + output wire o_interrupt, o_ppd; + // GPS interface + input i_gps_valid, i_gps_pps; + input [31:0] i_gps_ckspeed; + // Personal PPS + output wire o_rtc_pps; + + reg [23:0] clock; + reg [31:0] stopwatch, ckspeed; + reg [25:0] timer; + + wire ck_sel, tm_sel, sw_sel, al_sel; + assign ck_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr==2'b00)); + assign tm_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr==2'b01)); + assign sw_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr==2'b10)); + assign al_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr==2'b11)); + + reg [39:0] ck_counter; + reg ck_carry; + always @(posedge i_clk) + if ((i_gps_valid)&&(i_gps_pps)) + begin + ck_carry <= 0; + // Start our counter 2 clocks into the future. + // Why? Because if we hit the PPS, we'll be delayed + // one clock from true time. This (hopefully) locks + // us back onto true time. Further, if we end up + // off (i.e., go off before the GPS tick ...) then + // the GPS tick will put us back on track ... likewise + // we've got code following that should keep us from + // ever producing two PPS's per second. + ck_counter <= { 7'h00, ckspeed, 1'b0 }; + end else + { ck_carry, ck_counter }<=ck_counter+{ 8'h00, ckspeed }; + + reg ck_pps; + reg ck_ppm, ck_pph, ck_ppd; + reg [7:0] ck_sub; + initial clock = 24'h00000000; + always @(posedge i_clk) + if ((i_gps_pps)&&(i_gps_valid)&&(ck_sub[7])) + ck_pps <= 1'b1; + else if ((ck_carry)&&(ck_sub == 8'hff)) + ck_pps <= 1'b1; + else + ck_pps <= 1'b0; + + assign o_rtc_pps = ck_pps; + always @(posedge i_clk) + begin + if ((i_gps_valid)&&(i_gps_pps)) + ck_sub <= 0; + else if (ck_carry) + ck_sub <= ck_sub + 1; + + if (ck_pps) + begin // advance the seconds + if (clock[3:0] >= 4'h9) + clock[3:0] <= 4'h0; + else + clock[3:0] <= clock[3:0] + 4'h1; + if (clock[7:0] >= 8'h59) + clock[7:4] <= 4'h0; + else if (clock[3:0] >= 4'h9) + clock[7:4] <= clock[7:4] + 4'h1; + end + ck_ppm <= (clock[7:0] == 8'h59); + + if ((ck_pps)&&(ck_ppm)) + begin // advance the minutes + if (clock[11:8] >= 4'h9) + clock[11:8] <= 4'h0; + else + clock[11:8] <= clock[11:8] + 4'h1; + if (clock[15:8] >= 8'h59) + clock[15:12] <= 4'h0; + else if (clock[11:8] >= 4'h9) + clock[15:12] <= clock[15:12] + 4'h1; + end + ck_pph <= (clock[15:0] == 16'h5959); + + if ((ck_pps)&&(ck_pph)) + begin // advance the hours + if (clock[21:16] >= 6'h23) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= 2'h0; + end else if (clock[19:16] >= 4'h9) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= clock[21:20] + 2'h1; + end else begin + clock[19:16] <= clock[19:16] + 4'h1; + end + end + ck_ppd <= (clock[21:0] == 22'h235959); + + + if ((ck_sel)&&(i_wb_we)) + begin + if (8'hff != i_wb_data[7:0]) + begin + clock[7:0] <= i_wb_data[7:0]; + ck_ppm <= (i_wb_data[7:0] == 8'h59); + end + if (8'hff != i_wb_data[15:8]) + begin + clock[15:8] <= i_wb_data[15:8]; + ck_pph <= (i_wb_data[15:8] == 8'h59); + end + if (6'h3f != i_wb_data[21:16]) + clock[21:16] <= i_wb_data[21:16]; + clock[23:22] <= i_wb_data[25:24]; + if ((~i_gps_valid)&&(8'h00 == i_wb_data[7:0])) + ck_sub <= 8'h00; + end + end + + reg [21:0] ck_last_clock; + always @(posedge i_clk) + ck_last_clock <= clock[21:0]; + + reg tm_pps, tm_ppm, tm_int; + wire tm_stopped, tm_running, tm_alarm; + assign tm_stopped = ~timer[24]; + assign tm_running = timer[24]; + assign tm_alarm = timer[25]; + reg [23:0] tm_start; + reg [7:0] tm_sub; + initial tm_start = 24'h00; + initial timer = 26'h00; + initial tm_int = 1'b0; + initial tm_pps = 1'b0; + always @(posedge i_clk) + begin + if (ck_carry) + begin + tm_sub <= tm_sub + 1; + tm_pps <= (tm_sub == 8'hff); + end else + tm_pps <= 1'b0; + + if ((~tm_alarm)&&(tm_running)&&(tm_pps)) + begin // If we are running ... + timer[25] <= 1'b0; + if (timer[23:0] == 24'h00) + timer[25] <= 1'b1; + else if (timer[3:0] != 4'h0) + timer[3:0] <= timer[3:0]-4'h1; + else begin // last digit is a zero + timer[3:0] <= 4'h9; + if (timer[7:4] != 4'h0) + timer[7:4] <= timer[7:4]-4'h1; + else begin // last two digits are zero + timer[7:4] <= 4'h5; + if (timer[11:8] != 4'h0) + timer[11:8] <= timer[11:8]-4'h1; + else begin // last three digits are zero + timer[11:8] <= 4'h9; + if (timer[15:12] != 4'h0) + timer[15:12] <= timer[15:12]-4'h1; + else begin + timer[15:12] <= 4'h5; + if (timer[19:16] != 4'h0) + timer[19:16] <= timer[19:16]-4'h1; + else begin + // + timer[19:16] <= 4'h9; + timer[23:20] <= timer[23:20]-4'h1; + end + end + end + end + end + end + + if((~tm_alarm)&&(tm_running)) + begin + timer[25] <= (timer[23:0] == 24'h00); + tm_int <= (timer[23:0] == 24'h00); + end else tm_int <= 1'b0; + if (tm_alarm) + timer[24] <= 1'b0; + + if ((tm_sel)&&(i_wb_we)&&(tm_running)) // Writes while running + // Only allowed to stop the timer, nothing more + timer[24] <= i_wb_data[24]; + else if ((tm_sel)&&(i_wb_we)&&(tm_stopped)) // Writes while off + begin + timer[24] <= i_wb_data[24]; + if ((timer[24])||(i_wb_data[24])) + timer[25] <= 1'b0; + if (i_wb_data[23:0] != 24'h0000) + begin + timer[23:0] <= i_wb_data[23:0]; + tm_start <= i_wb_data[23:0]; + tm_sub <= 8'h00; + end else if (timer[23:0] == 24'h00) + begin // Resetting timer to last valid timer start val + timer[23:0] <= tm_start; + tm_sub <= 8'h00; + end + // Any write clears the alarm + timer[25] <= 1'b0; + end + end + + // + // Stopwatch functionality + // + // Setting bit '0' starts the stop watch, clearing it stops it. + // Writing to the register with bit '1' high will clear the stopwatch, + // and return it to zero provided that the stopwatch is stopped either + // before or after the write. Hence, writing a '2' to the device + // will always stop and clear it, whereas writing a '3' to the device + // will only clear it if it was already stopped. + reg sw_pps, sw_ppm, sw_pph; + reg [7:0] sw_sub; + wire sw_running; + assign sw_running = stopwatch[0]; + initial stopwatch = 32'h00000; + always @(posedge i_clk) + begin + sw_pps <= 1'b0; + if (sw_running) + begin + if (ck_carry) + begin + sw_sub <= sw_sub + 1; + sw_pps <= (sw_sub == 8'hff); + end + end + + stopwatch[7:1] <= sw_sub[7:1]; + + if (sw_pps) + begin // Second hand + if (stopwatch[11:8] >= 4'h9) + stopwatch[11:8] <= 4'h0; + else + stopwatch[11:8] <= stopwatch[11:8] + 4'h1; + + if (stopwatch[15:8] >= 8'h59) + stopwatch[15:12] <= 4'h0; + else if (stopwatch[11:8] >= 4'h9) + stopwatch[15:12] <= stopwatch[15:12] + 4'h1; + sw_ppm <= (stopwatch[15:8] == 8'h59); + end else sw_ppm <= 1'b0; + + if (sw_ppm) + begin // Minutes + if (stopwatch[19:16] >= 4'h9) + stopwatch[19:16] <= 4'h0; + else + stopwatch[19:16] <= stopwatch[19:16]+4'h1; + + if (stopwatch[23:16] >= 8'h59) + stopwatch[23:20] <= 4'h0; + else if (stopwatch[19:16] >= 4'h9) + stopwatch[23:20] <= stopwatch[23:20]+4'h1; + sw_pph <= (stopwatch[23:16] == 8'h59); + end else sw_pph <= 1'b0; + + if (sw_pph) + begin // And hours + if (stopwatch[27:24] >= 4'h9) + stopwatch[27:24] <= 4'h0; + else + stopwatch[27:24] <= stopwatch[27:24]+4'h1; + + if((stopwatch[27:24] >= 4'h9)&&(stopwatch[31:28] < 4'hf)) + stopwatch[31:28] <= stopwatch[27:24]+4'h1; + end + + if ((sw_sel)&&(i_wb_we)) + begin + stopwatch[0] <= i_wb_data[0]; + if((i_wb_data[1])&&((~stopwatch[0])||(~i_wb_data[0]))) + begin + stopwatch[31:1] <= 31'h00; + sw_sub <= 8'h00; + sw_pps <= 1'b0; + sw_ppm <= 1'b0; + sw_pph <= 1'b0; + end + end + end + + // + // The alarm code + // + // Set the alarm register to the time you wish the board to "alarm". + // The "alarm" will take place once per day at that time. At that + // time, the RTC code will generate a clock interrupt, and the CPU/host + // can come and see that the alarm tripped. + // + // + reg [21:0] alarm_time; + reg al_int, // The alarm interrupt line + al_enabled, // Whether the alarm is enabled + al_tripped; // Whether the alarm has tripped + initial al_enabled= 1'b0; + initial al_tripped= 1'b0; + always @(posedge i_clk) + begin + if ((al_sel)&&(i_wb_we)) + begin + // Only adjust the alarm hours if the requested hours + // are valid. This allows writes to the register, + // without a prior read, to leave these configuration + // bits alone. + if (i_wb_data[21:16] != 6'h3f) + alarm_time[21:16] <= i_wb_data[21:16]; + // Here's the same thing for the minutes: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[15:8] != 8'hff) + alarm_time[15:8] <= i_wb_data[15:8]; + // Here's the same thing for the seconds: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[7:0] != 8'hff) + alarm_time[7:0] <= i_wb_data[7:0]; + al_enabled <= i_wb_data[24]; + // Reset the alarm if a '1' is written to the tripped + // register, or if the alarm is disabled. + if ((i_wb_data[25])||(~i_wb_data[24])) + al_tripped <= 1'b0; + end + + al_int <= 1'b0; + if ((ck_last_clock != alarm_time)&&(clock[21:0] == alarm_time)&&(al_enabled)) + begin + al_tripped <= 1'b1; + al_int <= 1'b1; + end + end + + // + // The ckspeed register is equal to 2^48 divded by the number of + // clock ticks you expect per second. Adjust high for a slower + // clock, lower for a faster clock. In this fashion, a single + // real time clock RTL file can handle tracking the clock in any + // device. Further, because this is only the lower 32 bits of a + // 48 bit counter per seconds, the clock jitter is kept below + // 1 part in 65 thousand. + // + initial ckspeed = DEFAULT_SPEED; + // In the case of verilator, comment the above and uncomment the line + // below. The clock constant below is "close" to simulation time, + // meaning that my verilator simulation is running about 300x slower + // than board time. + // initial ckspeed = 32'd786432000; + always @(posedge i_clk) + if (i_gps_valid) + ckspeed <= i_gps_ckspeed; + + reg [15:0] h_sseg; + reg [3:1] dmask; + always @(posedge i_clk) + case(clock[23:22]) + 2'h1: begin h_sseg <= timer[15:0]; + if (tm_alarm) dmask <= 3'h7; + else begin + dmask[3] <= (12'h000 != timer[23:12]); // timer[15:12] + dmask[2] <= (16'h000 != timer[23: 8]); // timer[11: 8] + dmask[1] <= (20'h000 != timer[23: 4]); // timer[ 7: 4] + // dmask[0] <= 1'b1; // Always on + end end + 2'h2: begin h_sseg <= stopwatch[19:4]; + dmask[3] <= (12'h00 != stopwatch[27:16]); + dmask[2] <= (16'h000 != stopwatch[27:12]); + dmask[1] <= 1'b1; // Always on, stopwatch[11:8] + // dmask[0] <= 1'b1; // Always on, stopwatch[7:4] + end + 2'h3: begin h_sseg <= clock[15:0]; + dmask[3:1] <= 3'h7; + end + default: begin // 4'h0 + h_sseg <= { 2'b00, clock[21:8] }; + dmask[2:1] <= 2'b11; + dmask[3] <= (2'b00 != clock[21:20]); + end + endcase + + wire [31:0] w_sseg; + assign w_sseg[ 0] = (i_gps_valid)?(ck_sub[7:5]==3'h0):(~ck_sub[0]); + assign w_sseg[ 8] = (i_gps_valid)?(ck_sub[7:5]==3'h0):(~ck_sub[0]); + assign w_sseg[16] = (i_gps_valid)?(ck_sub[7:5]==3'h0):(~ck_sub[0]); + // assign w_sseg[ 8] = w_sseg[0]; + // assign w_sseg[16] = w_sseg[0]; + assign w_sseg[24] = 1'b0; + hexmap ha(i_clk, h_sseg[ 3: 0], w_sseg[ 7: 1]); + hexmap hb(i_clk, h_sseg[ 7: 4], w_sseg[15: 9]); + hexmap hc(i_clk, h_sseg[11: 8], w_sseg[23:17]); + hexmap hd(i_clk, h_sseg[15:12], w_sseg[31:25]); + + always @(posedge i_clk) + if ((tm_alarm || al_tripped)&&(ck_sub[7])) + o_sseg <= 32'h0000; + else + o_sseg <= { + (dmask[3])?w_sseg[31:24]:8'h00, + (dmask[2])?w_sseg[23:16]:8'h00, + (dmask[1])?w_sseg[15: 8]:8'h00, + w_sseg[ 7: 0] }; + + reg [17:0] ledreg; + always @(posedge i_clk) + if ((ck_pps)&&(ck_ppm)) + ledreg <= 18'h00; + else if (ck_carry) + ledreg <= ledreg + 18'h11; + assign o_led = (tm_alarm||al_tripped)?{ (16){ck_sub[7]}}: + { ledreg[17:10], + ledreg[10], ledreg[11], ledreg[12], ledreg[13], + ledreg[14], ledreg[15], ledreg[16], ledreg[17] }; + + assign o_interrupt = tm_int || al_int; + + // A once-per day strobe, on the last second of the day so that the + // the next clock is the first clock of the day. This is useful for + // connecting this module to a year/month/date date/calendar module. + assign o_ppd = (ck_ppd)&&(ck_pps); + + always @(posedge i_clk) + case(i_wb_addr) + 2'b00: o_data <= { ~i_gps_valid, 5'h0, clock[23:22], 2'b00, clock[21:0] }; + 2'b01: o_data <= { 6'h00, timer }; + 2'b10: o_data <= stopwatch; + 2'b11: o_data <= { 6'h00, al_tripped, al_enabled, 2'b00, alarm_time }; + endcase + +endmodule
diff --git a/verilog/rtl/rtc/rtclight.v b/verilog/rtl/rtc/rtclight.v new file mode 100644 index 0000000..51826d3 --- /dev/null +++ b/verilog/rtl/rtc/rtclight.v
@@ -0,0 +1,413 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Filename: rtclight.v +// +// Project: A Wishbone Controlled Real--time Clock Core +// +// Purpose: Implement a real time clock, including alarm, count--down +// timer, stopwatch, variable time frequency, and more. +// +// This is a light-weight version of the RTC found in this directory. +// Unlike the full RTC, this version does not support time hacks, seven +// segment display outputs, or LED's. It is an RTC for an internal core +// only. (That's how I was using it on one of my projects anyway ...) +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015, Gisselquist Technology, LLC +// +// This program is free software (firmware): you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. (It's in the $(ROOT)/doc directory. Run make with no +// target there if the PDF file isn't present.) If not, see +// <http://www.gnu.org/licenses/> for a copy. +// +// License: GPL, v3, as defined and found on www.gnu.org, +// http://www.gnu.org/licenses/gpl.html +// +// +/////////////////////////////////////////////////////////////////////////// +module rtclight(i_clk, + // Wishbone interface + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, + // o_wb_ack, o_wb_stb, o_wb_data, // no reads here + // // Button inputs + // i_btn, + // Output registers + o_data, // multiplexed based upon i_wb_addr + // Output controls + o_interrupt, + // A once-per-day strobe on the last clock of the day + o_ppd); + parameter DEFAULT_SPEED = 32'd2814750; // 100 Mhz + input i_clk; + input i_wb_cyc, i_wb_stb, i_wb_we; + input [2:0] i_wb_addr; + input [31:0] i_wb_data; + // input i_btn; + output reg [31:0] o_data; + output wire o_interrupt, o_ppd; + + reg [21:0] clock; + reg [31:0] stopwatch, ckspeed; + reg [25:0] timer; + + wire ck_sel, tm_sel, sw_sel, sp_sel, al_sel; + assign ck_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b000)); + assign tm_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b001)); + assign sw_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b010)); + assign al_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b011)); + assign sp_sel = ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr[2:0]==3'b100)); + + reg ck_carry; + reg [39:0] ck_counter; + initial ck_carry = 1'b0; + initial ck_counter = 40'h00; + always @(posedge i_clk) + { ck_carry, ck_counter } <= ck_counter + { 8'h00, ckspeed }; + + wire ck_pps; + reg ck_prepps, ck_ppm, ck_pph, ck_ppd; + reg [7:0] ck_sub; + initial clock = 22'h00000; + assign ck_pps = (ck_carry)&&(ck_prepps); + always @(posedge i_clk) + begin + if (ck_carry) + ck_sub <= ck_sub + 8'h1; + ck_prepps <= (ck_sub == 8'hff); + + if (ck_pps) + begin // advance the seconds + if (clock[3:0] >= 4'h9) + clock[3:0] <= 4'h0; + else + clock[3:0] <= clock[3:0] + 4'h1; + if (clock[7:0] >= 8'h59) + clock[7:4] <= 4'h0; + else if (clock[3:0] >= 4'h9) + clock[7:4] <= clock[7:4] + 4'h1; + end + ck_ppm <= (clock[7:0] == 8'h59); + + if ((ck_pps)&&(ck_ppm)) + begin // advance the minutes + if (clock[11:8] >= 4'h9) + clock[11:8] <= 4'h0; + else + clock[11:8] <= clock[11:8] + 4'h1; + if (clock[15:8] >= 8'h59) + clock[15:12] <= 4'h0; + else if (clock[11:8] >= 4'h9) + clock[15:12] <= clock[15:12] + 4'h1; + end + ck_pph <= (clock[15:0] == 16'h5959); + + if ((ck_pps)&&(ck_pph)) + begin // advance the hours + if (clock[21:16] >= 6'h23) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= 2'h0; + end else if (clock[19:16] >= 4'h9) + begin + clock[19:16] <= 4'h0; + clock[21:20] <= clock[21:20] + 2'h1; + end else begin + clock[19:16] <= clock[19:16] + 4'h1; + end + end + ck_ppd <= (clock[21:0] == 22'h235959); + + + if ((ck_sel)&&(i_wb_we)) + begin + if (8'hff != i_wb_data[7:0]) + begin + clock[7:0] <= i_wb_data[7:0]; + ck_ppm <= (i_wb_data[7:0] == 8'h59); + end + if (8'hff != i_wb_data[15:8]) + begin + clock[15:8] <= i_wb_data[15:8]; + ck_pph <= (i_wb_data[15:8] == 8'h59); + end + if (6'h3f != i_wb_data[21:16]) + clock[21:16] <= i_wb_data[21:16]; + if (8'h00 == i_wb_data[7:0]) + ck_sub <= 8'h00; + end + end + + // Clock updates take several clocks, so let's make sure we + // are only looking at a valid clock value before testing it. + reg [21:0] ck_last_clock; + always @(posedge i_clk) + ck_last_clock <= clock[21:0]; + + + reg tm_pps, tm_ppm, tm_int; + wire tm_stopped, tm_running, tm_alarm; + assign tm_stopped = ~timer[24]; + assign tm_running = timer[24]; + assign tm_alarm = timer[25]; + reg [23:0] tm_start; + reg [7:0] tm_sub; + initial tm_start = 24'h00; + initial timer = 26'h00; + initial tm_int = 1'b0; + initial tm_pps = 1'b0; + always @(posedge i_clk) + begin + if (ck_carry) + begin + tm_sub <= tm_sub + 8'h1; + tm_pps <= (tm_sub == 8'hff); + end else + tm_pps <= 1'b0; + + if ((~tm_alarm)&&(tm_running)&&(tm_pps)) + begin // If we are running ... + timer[25] <= 1'b0; + if (timer[23:0] == 24'h00) + timer[25] <= 1'b1; + else if (timer[3:0] != 4'h0) + timer[3:0] <= timer[3:0]-4'h1; + else begin // last digit is a zero + timer[3:0] <= 4'h9; + if (timer[7:4] != 4'h0) + timer[7:4] <= timer[7:4]-4'h1; + else begin // last two digits are zero + timer[7:4] <= 4'h5; + if (timer[11:8] != 4'h0) + timer[11:8] <= timer[11:8]-4'h1; + else begin // last three digits are zero + timer[11:8] <= 4'h9; + if (timer[15:12] != 4'h0) + timer[15:12] <= timer[15:12]-4'h1; + else begin + timer[15:12] <= 4'h5; + if (timer[19:16] != 4'h0) + timer[19:16] <= timer[19:16]-4'h1; + else begin + // + timer[19:16] <= 4'h9; + timer[23:20] <= timer[23:20]-4'h1; + end + end + end + end + end + end + + if((~tm_alarm)&&(tm_running)) + begin + timer[25] <= (timer[23:0] == 24'h00); + tm_int <= (timer[23:0] == 24'h00); + end else tm_int <= 1'b0; + if (tm_alarm) + timer[24] <= 1'b0; + + if ((tm_sel)&&(i_wb_we)&&(tm_running)) // Writes while running + // Only allowed to stop the timer, nothing more + timer[24] <= i_wb_data[24]; + else if ((tm_sel)&&(i_wb_we)&&(tm_stopped)) // Writes while off + begin + timer[24] <= i_wb_data[24]; + if ((timer[24])||(i_wb_data[24])) + timer[25] <= 1'b0; + if (i_wb_data[23:0] != 24'h0000) + begin + timer[23:0] <= i_wb_data[23:0]; + tm_start <= i_wb_data[23:0]; + tm_sub <= 8'h00; + end else if (timer[23:0] == 24'h00) + begin // Resetting timer to last valid timer start val + timer[23:0] <= tm_start; + tm_sub <= 8'h00; + end + // Any write clears the alarm + timer[25] <= 1'b0; + end + end + + // + // Stopwatch functionality + // + // Setting bit '0' starts the stop watch, clearing it stops it. + // Writing to the register with bit '1' high will clear the stopwatch, + // and return it to zero provided that the stopwatch is stopped either + // before or after the write. Hence, writing a '2' to the device + // will always stop and clear it, whereas writing a '3' to the device + // will only clear it if it was already stopped. + reg sw_pps, sw_ppm, sw_pph; + reg [7:0] sw_sub; + wire sw_running; + assign sw_running = stopwatch[0]; + initial stopwatch = 32'h00000; + always @(posedge i_clk) + begin + sw_pps <= 1'b0; + if (sw_running) + begin + if (ck_carry) + begin + sw_sub <= sw_sub + 8'h1; + sw_pps <= (sw_sub == 8'hff); + end + end + + stopwatch[7:1] <= sw_sub[7:1]; + + if (sw_pps) + begin // Second hand + if (stopwatch[11:8] >= 4'h9) + stopwatch[11:8] <= 4'h0; + else + stopwatch[11:8] <= stopwatch[11:8] + 4'h1; + + if (stopwatch[15:8] >= 8'h59) + stopwatch[15:12] <= 4'h0; + else if (stopwatch[11:8] >= 4'h9) + stopwatch[15:12] <= stopwatch[15:12] + 4'h1; + sw_ppm <= (stopwatch[15:8] == 8'h59); + end else sw_ppm <= 1'b0; + + if (sw_ppm) + begin // Minutes + if (stopwatch[19:16] >= 4'h9) + stopwatch[19:16] <= 4'h0; + else + stopwatch[19:16] <= stopwatch[19:16]+4'h1; + + if (stopwatch[23:16] >= 8'h59) + stopwatch[23:20] <= 4'h0; + else if (stopwatch[19:16] >= 4'h9) + stopwatch[23:20] <= stopwatch[23:20]+4'h1; + sw_pph <= (stopwatch[23:16] == 8'h59); + end else sw_pph <= 1'b0; + + if (sw_pph) + begin // And hours + if (stopwatch[27:24] >= 4'h9) + stopwatch[27:24] <= 4'h0; + else + stopwatch[27:24] <= stopwatch[27:24]+4'h1; + + if((stopwatch[27:24] >= 4'h9)&&(stopwatch[31:28] < 4'hf)) + stopwatch[31:28] <= stopwatch[27:24]+4'h1; + end + + if ((sw_sel)&&(i_wb_we)) + begin + stopwatch[0] <= i_wb_data[0]; + if((i_wb_data[1])&&((~stopwatch[0])||(~i_wb_data[0]))) + begin + stopwatch[31:1] <= 31'h00; + sw_sub <= 8'h00; + sw_pps <= 1'b0; + sw_ppm <= 1'b0; + sw_pph <= 1'b0; + end + end + end + + // + // The alarm code + // + // Set the alarm register to the time you wish the board to "alarm". + // The "alarm" will take place once per day at that time. At that + // time, the RTC code will generate a clock interrupt, and the CPU/host + // can come and see that the alarm tripped. + // + // + reg [21:0] alarm_time; + reg al_int, // The alarm interrupt line + al_enabled, // Whether the alarm is enabled + al_tripped; // Whether the alarm has tripped + initial al_enabled= 1'b0; + initial al_tripped= 1'b0; + always @(posedge i_clk) + begin + if ((al_sel)&&(i_wb_we)) + begin + // Only adjust the alarm hours if the requested hours + // are valid. This allows writes to the register, + // without a prior read, to leave these configuration + // bits alone. + if (i_wb_data[21:16] != 6'h3f) + alarm_time[21:16] <= i_wb_data[21:16]; + // Here's the same thing for the minutes: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[15:8] != 8'hff) + alarm_time[15:8] <= i_wb_data[15:8]; + // Here's the same thing for the seconds: only adjust + // the alarm minutes if the new bits are not all 1's. + if (i_wb_data[7:0] != 8'hff) + alarm_time[7:0] <= i_wb_data[7:0]; + al_enabled <= i_wb_data[24]; + // Reset the alarm if a '1' is written to the tripped + // register, or if the alarm is disabled. + if ((i_wb_data[25])||(~i_wb_data[24])) + al_tripped <= 1'b0; + end + + al_int <= 1'b0; + if ((ck_last_clock != alarm_time)&&(clock[21:0] == alarm_time) + &&(al_enabled)) + begin + al_tripped <= 1'b1; + al_int <= 1'b1; + end + end + + // + // The ckspeed register is equal to 2^48 divded by the number of + // clock ticks you expect per second. Adjust high for a slower + // clock, lower for a faster clock. In this fashion, a single + // real time clock RTL file can handle tracking the clock in any + // device. Further, because this is only the lower 32 bits of a + // 48 bit counter per seconds, the clock jitter is kept below + // 1 part in 65 thousand. + // + initial ckspeed = DEFAULT_SPEED; // 2af31e = 2^48 / 100e6 MHz + // In the case of verilator, comment the above and uncomment the line + // below. The clock constant below is "close" to simulation time, + // meaning that my verilator simulation is running about 300x slower + // than board time. + // initial ckspeed = 32'd786432000; + always @(posedge i_clk) + if ((sp_sel)&&(i_wb_we)) + ckspeed <= i_wb_data; + + assign o_interrupt = tm_int || al_int; + + // A once-per day strobe, on the last second of the day so that the + // the next clock is the first clock of the day. This is useful for + // connecting this module to a year/month/date date/calendar module. + assign o_ppd = (ck_ppd)&&(ck_pps); + + always @(posedge i_clk) + case(i_wb_addr[2:0]) + 3'b000: o_data <= { 10'h0, ck_last_clock }; + 3'b001: o_data <= { 6'h00, timer }; + 3'b010: o_data <= stopwatch; + 3'b011: o_data <= { 6'h00, al_tripped, al_enabled, 2'b00, alarm_time }; + 3'b100: o_data <= ckspeed; + default: o_data <= 32'h000; + endcase + +endmodule