Merge pull request #2348 from colinleroy/time-functions-pass-2

Convert more time functions to asm
This commit is contained in:
Bob Andrews
2024-01-23 23:45:10 +01:00
committed by GitHub
11 changed files with 853 additions and 448 deletions

View File

@@ -1,22 +0,0 @@
/*
** _is_leap_year.h
**
** (C) Copyright 2024, Colin Leroy-Mira <colin@colino.net>
**
*/
#ifndef __IS_LEAP_YEAR_H
#define __IS_LEAP_YEAR_H
unsigned char __fastcall__ IsLeapYear (unsigned char Year);
/* Returns 1 if the given year is a leap year. Expects a year from 0 to 206,
* without 1900 added */
/* End of _is_leap_year.h */
#endif

View File

@@ -1,23 +0,0 @@
;
; Colin Leroy-Mira, 2024
;
; unsigned char __fastcall__ IsLeapYear (unsigned char Year)
; Returns 1 in A if the given year is a leap year. Expects a year from 0 to 206,
; without 1900 added.
;
.export _IsLeapYear
_IsLeapYear:
ldx #$00 ; Prepare X for rts
cmp #$00 ; Y 0 (1900) is not a leap year
beq NotLeap
cmp #$C8 ; Y 200 (2100) is not a leap year
beq NotLeap
and #$03 ; Year % 4 == 0 means leap year
bne NotLeap
lda #$01 ; Return 1
rts
NotLeap:
lda #$00 ; Return 0
rts

View File

@@ -1,64 +0,0 @@
/*****************************************************************************/
/* */
/* gmtime.c */
/* */
/* Convert calendar time into broken down time in UTC */
/* */
/* */
/* */
/* (C) 2002 Ullrich von Bassewitz */
/* Wacholderweg 14 */
/* D-70597 Stuttgart */
/* EMail: uz@musoftware.de */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
/* warranty. In no event will the authors be held liable for any damages */
/* arising from the use of this software. */
/* */
/* Permission is granted to anyone to use this software for any purpose, */
/* including commercial applications, and to alter it and redistribute it */
/* freely, subject to the following restrictions: */
/* */
/* 1. The origin of this software must not be misrepresented; you must not */
/* claim that you wrote the original software. If you use this software */
/* in a product, an acknowledgment in the product documentation would be */
/* appreciated but is not required. */
/* 2. Altered source versions must be plainly marked as such, and must not */
/* be misrepresented as being the original software. */
/* 3. This notice may not be removed or altered from any source */
/* distribution. */
/* */
/*****************************************************************************/
#include <time.h>
/*****************************************************************************/
/* Code */
/*****************************************************************************/
struct tm* __fastcall__ _time_t_to_tm (const time_t t)
{
static struct tm timebuf;
/* Since our ints are just 16 bits, split the given time into seconds,
** hours and days. Each of the values will fit in a 16 bit variable.
** The mktime routine will then do the rest.
*/
timebuf.tm_sec = t % 3600;
timebuf.tm_min = 0;
timebuf.tm_hour = (t / 3600) % 24;
timebuf.tm_mday = (t / (3600UL * 24UL)) + 1;
timebuf.tm_mon = 0;
timebuf.tm_year = 70; /* Base value is 1/1/1970 */
/* Call mktime to do the final conversion */
mktime (&timebuf);
/* Return the result */
return &timebuf;
}

View File

@@ -0,0 +1,129 @@
;
; Colin Leroy-Mira, 2024
;
; struct tm* __fastcall__ _time_t_to_tm (const time_t t)
;
; Helper to gmtime and localtime. Breaks down a number of
; seconds since Jan 1, 1970 into days, hours and seconds,
; so that each of them fits in 16 bits; passes the
; result to _mktime which fixes all values in the struct,
; and returns a pointer to the struct to callers.
;
.export __time_t_to_tm
.import udiv32, _mktime
.importzp sreg, tmp3, ptr1, ptr2, ptr3, ptr4
.include "time.inc"
.macpack cpu
__time_t_to_tm:
; Divide number of seconds since epoch, in ptr1:sreg,
; by 86400 to get the number of days since epoch, and
; the number of seconds today in the remainder.
; Load t as dividend (sreg is already set by the caller)
sta ptr1
stx ptr1+1
; Load 86400 as divisor
lda #$80
sta ptr3
lda #$51
sta ptr3+1
lda #$01
sta ptr4
lda #$00
sta ptr4+1
; Clear TM buf while we have zero in A
ldx #.sizeof(tm)-1
: sta TM,x
dex
bpl :-
; Divide t/86400
jsr udiv32
; Store the quotient (the number of full days), and increment
; by one as epoch starts at day 1.
clc
lda ptr1
adc #1
sta TM + tm::tm_mday
lda ptr1+1
adc #0
sta TM + tm::tm_mday+1
; Now divide the number of remaining seconds by 3600,
; to get the number of hours, and the seconds in the
; current hour, in neat 16-bit integers.
; Load the previous division's remainder (in ptr2:tmp3:tmp4)
; as dividend
lda ptr2
sta ptr1
lda ptr2+1
sta ptr1+1
lda tmp3
sta sreg
; We ignore the high byte stored in tmp4 because it will be
; zero. We'll zero sreg+1 right below, when we'll have
; a convenient zero already in A.
; Load divisor
lda #<3600
sta ptr3
lda #>3600
sta ptr3+1
; Zero the two high bytes of the divisor and the high byte
; of the dividend.
.if .cpu .bitand CPU_ISET_65SC02
stz ptr4
stz ptr4+1
stz sreg+1
.else
lda #$00
sta ptr4
sta ptr4+1
sta sreg+1
.endif
; Do the division
jsr udiv32
; Store year
lda #70
sta TM + tm::tm_year
; Store hours (the quotient of the last division)
lda ptr1
sta TM + tm::tm_hour
lda ptr1+1
sta TM + tm::tm_hour+1
; Store seconds (the remainder of the last division)
lda ptr2
sta TM + tm::tm_sec
lda ptr2+1
sta TM + tm::tm_sec+1
; The rest of the struct tm fields are zero. mktime
; will take care of shifting extra seconds to minutes,
; and extra days to months and years.
; Call mktime
lda #<TM
ldx #>TM
jsr _mktime
; And return our pointer
lda #<TM
ldx #>TM
rts
.bss
TM: .tag tm

View File

@@ -1,59 +0,0 @@
/*****************************************************************************/
/* */
/* asctime.c */
/* */
/* Convert a broken down time into a string */
/* */
/* */
/* */
/* (C) 2002 Ullrich von Bassewitz */
/* Wacholderweg 14 */
/* D-70597 Stuttgart */
/* EMail: uz@musoftware.de */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
/* warranty. In no event will the authors be held liable for any damages */
/* arising from the use of this software. */
/* */
/* Permission is granted to anyone to use this software for any purpose, */
/* including commercial applications, and to alter it and redistribute it */
/* freely, subject to the following restrictions: */
/* */
/* 1. The origin of this software must not be misrepresented; you must not */
/* claim that you wrote the original software. If you use this software */
/* in a product, an acknowledgment in the product documentation would be */
/* appreciated but is not required. */
/* 2. Altered source versions must be plainly marked as such, and must not */
/* be misrepresented as being the original software. */
/* 3. This notice may not be removed or altered from any source */
/* distribution. */
/* */
/*****************************************************************************/
#include <stdio.h>
#include <time.h>
/*****************************************************************************/
/* Code */
/*****************************************************************************/
/*
CAUTION: we need to reserve enough space to be able to hold the maximum
length string:
1234567890123456789012345678901234567
"Wednesday September ..1 00:00:00 1970"
*/
char* __fastcall__ asctime (const struct tm* timep)
{
static char buf[38];
/* Format into given buffer and return the result */
return strftime (buf, sizeof (buf), "%c\n", timep)? buf : 0;
}

81
libsrc/common/asctime.s Normal file
View File

@@ -0,0 +1,81 @@
;
; Colin Leroy-Mira, 2024
;
; char* __fastcall__ asctime (const struct tm* timep)
;
.export _asctime
.import _strftime, pushax
.importzp ptr1
.include "time.inc"
.macpack cpu
; ------------------------------------------------------------------------
; Special values
; We need to be able to store up to 38 bytes:
; 1234567890123456789012345678901234567
; "Wednesday September ..1 00:00:00 1970"
MAX_BUF_LEN = 38
; ------------------------------------------------------------------------
; Code
_asctime:
; Backup timep
.if (.cpu .bitand ::CPU_ISET_65SC02)
pha
phx
.else
sta ptr1
stx ptr1+1
.endif
; Push buf
lda #<buf
ldx #>buf
jsr pushax
; Push sizeof(buf)
lda #<MAX_BUF_LEN
ldx #>MAX_BUF_LEN
jsr pushax
; Push format string
lda #<fmt
ldx #>fmt
jsr pushax
; Restore timep
.if (.cpu .bitand ::CPU_ISET_65SC02)
plx
pla
.else
lda ptr1
ldx ptr1+1
.endif
; Call formatter
jsr _strftime
; Check return status
bne :+
cpx #$00
bne :+
rts
: lda #<buf
ldx #>buf
rts
.data
fmt: .byte '%'
.byte 'c'
.byte $0A
.byte $00
.bss
buf: .res MAX_BUF_LEN

View File

@@ -3,7 +3,7 @@
; 2002-10-22, Greg King
;
; This signed-division function returns both the quotient and the remainder,
; in this structure:
; in this structure: (quotient in sreg, remainder in AX)
;
; typedef struct {
; int rem, quot;

View File

@@ -1,184 +0,0 @@
/*****************************************************************************/
/* */
/* mktime.c */
/* */
/* Make calendar time from broken down time and cleanup */
/* */
/* */
/* */
/* (C) 2002 Ullrich von Bassewitz */
/* Wacholderweg 14 */
/* D-70597 Stuttgart */
/* EMail: uz@musoftware.de */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
/* warranty. In no event will the authors be held liable for any damages */
/* arising from the use of this software. */
/* */
/* Permission is granted to anyone to use this software for any purpose, */
/* including commercial applications, and to alter it and redistribute it */
/* freely, subject to the following restrictions: */
/* */
/* 1. The origin of this software must not be misrepresented; you must not */
/* claim that you wrote the original software. If you use this software */
/* in a product, an acknowledgment in the product documentation would be */
/* appreciated but is not required. */
/* 2. Altered source versions must be plainly marked as such, and must not */
/* be misrepresented as being the original software. */
/* 3. This notice may not be removed or altered from any source */
/* distribution. */
/* */
/*****************************************************************************/
#include <limits.h>
#include <stdlib.h>
#include <time.h>
#include "_is_leap_year.h"
/*****************************************************************************/
/* Data */
/*****************************************************************************/
#define JANUARY 0
#define FEBRUARY 1
#define DECEMBER 11
#define JAN_1_1970 4 /* 1/1/1970 is a thursday */
static const unsigned char MonthLength [] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static const unsigned MonthDays [] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
/*****************************************************************************/
/* Code */
/*****************************************************************************/
time_t __fastcall__ mktime (register struct tm* TM)
/* Make a time in seconds since 1/1/1970 from the broken down time in TM.
** A call to mktime does also correct the time in TM to contain correct
** values.
*/
{
register div_t D;
static int Max;
static unsigned DayCount;
/* Check if TM is valid */
if (TM == 0) {
/* Invalid data */
return (time_t) -1L;
}
/* Adjust seconds. */
D = div (TM->tm_sec, 60);
TM->tm_sec = D.rem;
/* Adjust minutes */
TM->tm_min += D.quot;
D = div (TM->tm_min, 60);
TM->tm_min = D.rem;
/* Adjust hours */
TM->tm_hour += D.quot;
D = div (TM->tm_hour, 24);
TM->tm_hour = D.rem;
/* Adjust days */
TM->tm_mday += D.quot;
/* Adjust year */
while (1) {
Max = 365UL + IsLeapYear (TM->tm_year);
if ((unsigned int)TM->tm_mday > Max) {
++TM->tm_year;
TM->tm_mday -= Max;
} else {
break;
}
}
/* Adjust month and year. This is an iterative process, since changing
** the month will change the allowed days for this month.
*/
while (1) {
/* Make sure, month is in the range 0..11 */
D = div (TM->tm_mon, 12);
TM->tm_mon = D.rem;
TM->tm_year += D.quot;
/* Now check if mday is in the correct range, if not, correct month
** and eventually year and repeat the process.
*/
if (TM->tm_mon == FEBRUARY && IsLeapYear (TM->tm_year)) {
Max = 29;
} else {
Max = MonthLength[TM->tm_mon];
}
if ((unsigned int)TM->tm_mday > Max) {
/* Must correct month and eventually, year */
if (TM->tm_mon == DECEMBER) {
TM->tm_mon = JANUARY;
++TM->tm_year;
} else {
++TM->tm_mon;
}
TM->tm_mday -= Max;
} else {
/* Done */
break;
}
}
/* Ok, all time/date fields are now correct. Calculate the days in this
** year.
*/
TM->tm_yday = MonthDays[TM->tm_mon] + TM->tm_mday - 1;
if (TM->tm_mon > FEBRUARY && IsLeapYear (TM->tm_year)) {
++TM->tm_yday;
}
/* Calculate days since 1/1/1970. In the complete epoch (1/1/1970 to
** somewhere in 2106) all years dividable by 4 are leap years(1),
** so dividing by 4 gives the days that must be added because of leap years.
** (and the last leap year before 1970 was 1968)
** (1): Exception on 2100, which is not leap, and handled just after.
*/
DayCount = ((unsigned) (TM->tm_year-70)) * 365U +
(((unsigned) (TM->tm_year-(68+1))) / 4) +
TM->tm_yday;
/* Handle the 2100 exception */
if (TM->tm_year == 200 && TM->tm_mon > FEBRUARY) {
DayCount--;
} else if (TM->tm_year > 200) {
DayCount--;
}
/* Calculate the weekday */
TM->tm_wday = (JAN_1_1970 + DayCount) % 7;
/* No (US) daylight saving (for now) */
TM->tm_isdst = 0;
/* Return seconds since 1970 */
return DayCount * 86400UL +
((unsigned) TM->tm_hour) * 3600UL +
((unsigned) TM->tm_min) * 60U +
((unsigned) TM->tm_sec) -
_tz.timezone;
}

476
libsrc/common/mktime.s Normal file
View File

@@ -0,0 +1,476 @@
;
; Colin Leroy-Mira, 2024
;
; time_t __fastcall__ mktime (register struct tm* TM)
;
; Converts a struct tm to a time_t timestamp, making sure
; day, month, year, hour, minute and seconds are in the
; correct range.
;
.export _mktime
.import __tz
.import pushax, pusha0, pusheax
.import shrax2, _div, tosumulax, tosumodax, tossubeax, tosaddeax, tosumuleax
.importzp ptr2, tmp3, sreg
.include "time.inc"
; ------------------------------------------------------------------------
; Special values
FEBRUARY = 1
MARCH = 2
JAN_1_1970 = 4
N_SEC = 60
N_MIN = 60
N_HOUR = 24
N_MON = 12
N_DAY_YEAR = 365
; ------------------------------------------------------------------------
; Helpers
; Helper to shift overflows from one field to the next
; Current field in Y, divisor in A
; Keeps remainder in current field, and adds the quotient
; to the next one
adjust_field:
pha ; Push divisor
iny ; Point to high byte of current field
lda (ptr2),y
tax
dey
sty tmp3 ; Store current field (_div will mess with
lda (ptr2),y ; tmp1 and tmp2)
jsr pushax
pla ; Load divisor
ldx #$00
jsr _div
ldy tmp3 ; Store remainder in current field
sta (ptr2),y
iny
txa
sta (ptr2),y
lda sreg ; Add quotient to next field
iny
clc
adc (ptr2),y
sta (ptr2),y
iny
lda sreg+1
adc (ptr2),y
sta (ptr2),y
rts
; Returns 1 in A if the given year is a leap year. Expects a year
; from 0 to 206, without 1900 added.
is_leap_year:
cmp #$00 ; Y 0 (1900) is not a leap year
beq not_leap
cmp #$C8 ; Y 200 (2100) is not a leap year
beq not_leap
and #$03 ; Year % 4 == 0 means leap year
bne not_leap
lda #$01 ; Return 1
rts
not_leap:
lda #$00 ; Return 0
rts
; Returns the number of days in the current month/year in A
get_days_in_month:
ldy #tm::tm_mon
lda (ptr2),y
tax
lda months_len,x
cpx #FEBRUARY
beq :+
rts
: tax
ldy #tm::tm_year ; Adjust for leap years
lda (ptr2),y
jsr is_leap_year
beq :+
inx
: txa
rts
; Add AX to counter
addaxcounter:
clc
adc Counter
sta Counter ; Store in Counter
txa
adc Counter+1
sta Counter+1
rts
; Helpers for long chain of arithmetic on day counter.
; Reload Counter and push it on the stack
load_and_push_counter:
lda Counter+3
sta sreg+1
lda Counter+2
sta sreg
lda Counter
ldx Counter+1
jsr pusheax
rts
; Store result in AX:sreg to Counter
store_counter:
sta Counter
stx Counter+1
lda sreg
sta Counter+2
lda sreg+1
sta Counter+3
rts
; ------------------------------------------------------------------------
; Code
_mktime:
sta ptr2 ; Store struct to ptr2, which arithmetic
stx ptr2+1 ; functions won't touch
; Check pointer validity
ora ptr2+1
bne :+
lda #$FF
tax
sta sreg
sta sreg+1
rts
; Adjust seconds
: ldy #tm::tm_sec
lda #N_SEC
jsr adjust_field
; Adjust minutes
ldy #tm::tm_min
lda #N_MIN
jsr adjust_field
; Adjust hours
ldy #tm::tm_hour
lda #N_HOUR
jsr adjust_field
;Shift one year as long as tm_mday is more than a year
ldy #tm::tm_year
lda (ptr2),y
dec_by_year:
jsr is_leap_year ; Compute max numbers of days in year
clc
adc #<N_DAY_YEAR ; No care about carry,
sta Max ; 365+1 doesn't overflow low byte
ldy #tm::tm_mday+1 ; Do we have more days in store?
lda (ptr2),y
cmp #>N_DAY_YEAR
beq :+ ; High byte equal, check low byte
bcs do_year_dec ; High byte greater, decrement
bcc dec_by_month ; Low byte lower, we're done
: dey
lda (ptr2),y
cmp Max
bcc dec_by_month
beq dec_by_month
do_year_dec:
; Decrement days
ldy #tm::tm_mday
lda (ptr2),y
sbc Max ; Carry already set
sta (ptr2),y
iny
lda (ptr2),y
sbc #>N_DAY_YEAR
sta (ptr2),y
; Increment year
ldy #tm::tm_year
lda (ptr2),y
clc
adc #1
sta (ptr2),y ; No carry possible here either
bcc dec_by_year ; bra, go check next year
dec_by_month:
; We're done decrementing days by full years, now do it
; month per month.
ldy #tm::tm_mon
lda #N_MON
jsr adjust_field
; Get max day for this month
jsr get_days_in_month
sta Max
; So, do we have more days than this month?
ldy #tm::tm_mday+1
lda (ptr2),y
bne do_month_dec ; High byte not zero, sure we do
dey
lda (ptr2),y
cmp Max
bcc calc_tm_yday ; No
beq calc_tm_yday
do_month_dec:
; Decrement days
ldy #tm::tm_mday
lda (ptr2),y
sec
sbc Max
sta (ptr2),y
iny
lda (ptr2),y
sbc #$00
sta (ptr2),y
; Increment month
ldy #tm::tm_mon
lda (ptr2),y
clc
adc #1
sta (ptr2),y
bne dec_by_month ; Check next month
calc_tm_yday:
; We finished decrementing tm_mday and have put it in the correct
; year/month range. Now compute the day of the year.
ldy #tm::tm_mday ; Get current day of month
lda (ptr2),y
sta Counter ; Store it in Counter
lda #$00 ; Init counter high bytes
sta Counter+1
sta Counter+2
sta Counter+3
ldy #tm::tm_mon ; Get current month
lda (ptr2),y
asl
tax
clc
lda yday_by_month,x ; Get yday for this month's start
adc Counter ; Add it to counter
sta Counter
inx
lda yday_by_month,x
adc Counter+1
sta Counter+1
ldy #tm::tm_year ; Adjust for leap years (if after feb)
lda (ptr2),y
jsr is_leap_year
beq dec_counter
ldy #tm::tm_mon ; Leap year, get current month
lda (ptr2),y
cmp #MARCH
bcs store_yday
dec_counter:
lda Counter ; Decrease counter by one (yday starts at 0),
bne :+ ; unless we're after february in a leap year
dec Counter+1
: dec Counter
store_yday:
ldy #tm::tm_yday ; Store tm_yday
lda Counter
sta (ptr2),y
iny
lda Counter+1
sta (ptr2),y
; Now calculate total day count since epoch with the formula:
; ((unsigned) (TM->tm_year-70)) * 365U + (number of days per year since 1970)
; (((unsigned) (TM->tm_year-(68+1))) / 4) + (one extra day per leap year since 1970)
; TM->tm_yday (number of days in this year)
ldy #tm::tm_year ; Get full years
lda (ptr2),y
sec
sbc #70
ldx #0
jsr pushax
lda #<N_DAY_YEAR
ldx #>N_DAY_YEAR
jsr tosumulax
jsr addaxcounter
; Add one day per leap year
ldy #tm::tm_year ; Get full years
lda (ptr2),y
sec
sbc #69
ldx #0
jsr shrax2 ; Divide by 4
jsr addaxcounter
; Handle the 2100 exception (which was considered leap by "Add one day
; per leap year" just before)
ldy #tm::tm_year ; Get full years
lda (ptr2),y
cmp #201
bcc finish_calc ; <= 200, nothing to do
lda Counter
bne :+
dec Counter+1
: dec Counter
finish_calc:
; Now we can compute the weekday.
lda Counter
clc
adc #JAN_1_1970
pha
lda Counter+1
adc #0
tax
pla
jsr pushax
lda #7 ; Modulo 7
ldx #0
jsr tosumodax
ldy #tm::tm_wday ; Store tm_wday
sta (ptr2),y
iny
txa
sta (ptr2),y
; DST
lda #$00 ; Store tm_isdst
ldy #tm::tm_isdst
sta (ptr2),y
iny
sta (ptr2),y
; Our struct tm is all fixed and every field calculated.
; We can finally count seconds according to this formula:
; seconds = (full days since epoch) * 86400UL +
; ((unsigned) TM->tm_hour) * 3600UL +
; ((unsigned) TM->tm_min) * 60U +
; ((unsigned) TM->tm_sec) -
; _tz.timezone;
; We already have the number of days since epoch in our counter,
; from just before when we computed tm_wday. Reuse it.
jsr load_and_push_counter
lda #$00 ; Multiply by 86400
sta sreg+1
lda #$01
sta sreg
lda #$80
ldx #$51
jsr tosumuleax
jsr store_counter ; Store into counter
; Push counter to add 3600 * hours to it
jsr load_and_push_counter
ldx #$00 ; Load hours
stx sreg
stx sreg+1
ldy #tm::tm_hour
lda (ptr2),y
jsr pusheax ; Push
ldx #$00 ; Load 3600
stx sreg
stx sreg+1
lda #<3600
ldx #>3600
jsr tosumuleax ; Multiply (pops the pushed hours)
jsr tosaddeax ; Add to counter (pops the pushed counter)
jsr store_counter ; Store counter
; Push counter to add 60 * min to it
jsr load_and_push_counter
ldy #tm::tm_min ; Load minutes
lda (ptr2),y
jsr pusha0 ; Push
lda #N_MIN
ldx #0
stx sreg
stx sreg+1
jsr tosumulax ; Multiply
jsr tosaddeax ; Add to pushed counter
jsr store_counter ; Store
; Add seconds
jsr load_and_push_counter
ldy #tm::tm_sec ; Load seconds
lda (ptr2),y
ldx #0
stx sreg
stx sreg+1
jsr tosaddeax ; Simple addition there
; No need to store/load/push the counter here, simply to push it
; for the last substraction
jsr pusheax
; Substract timezone
lda __tz+1+3
sta sreg+1
lda __tz+1+2
sta sreg
ldx __tz+1+1
lda __tz+1
jsr tossubeax
; And we're done!
rts
.data
months_len:
.byte 31
.byte 28
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
yday_by_month:
.word 0
.word 31
.word 59
.word 90
.word 120
.word 151
.word 181
.word 212
.word 243
.word 273
.word 304
.word 334
.bss
Max: .res 1 ; We won't need a high byte
Counter:
.res 4