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