Close

One More Thing

A project log for 2:5 Scale KENBAK-1 Personal Computer Reproduction

Make a working reproduction of the venerable KENBAK-1 with a fully integrated development environment including an Assembler and Debugger.

Michael GardiMichael Gardi 05/07/2021 at 19:340 Comments

When John Blankenbaker was demonstrating his KENBAK-1 Personal Computer back in 1971, one of the programs he always showed was a Day of the Week calculator. Given any date, it can tell you what day of the week that date fell on. It was something that was pretty cool that everyone could relate to.

Well I didn't feel that my KENBAK-2/5 reproduction would be quite complete until it could do the same. It was a fun programming challenge that exercised a lot more of the capabilities of my machine, and in fact uncovered a few minor issues with my emulator software:

I feel a lot more confident now that my reproduction is a very close work-a-like to the original.  Here is the code:

; Program to calculate the day of the week for any date. To start this program you will
; have to input the date in four parts: Century, Year, Month, and Day. Each of the parts
; is entered as a two digit Binary Coded Decimal number (ie. the first digit will occupy 
; bits 7-4 as a binary number, and the second digit bits 3-0) using the front panel data
; buttons. The steps to run this program are:
;
; 1) Set the PC register (at address 3) to 4.
; 2) Clear the input data then enter the date Century.
; 3) Press Start.
; 4) Clear the input data then enter the date Year.
; 5) Press Start.
; 6) Clear the input data then enter the date Month.
; 7) Press Start.
; 8) Clear the input data then enter the date Day.
; 9) Press Start.
;
; The day of the week will be returned via the data lamps using the following encoding:
; 
;      7-Sunday 6-Monday 5-Tuesday 4-Wednesday 3-Thursday 2-Friday 1-Saturday
;
; All lamps turned on means the last item entered was invalid and you have to restart.
;
;
; Get the date we want the day for.
;
	load	A,INPUT			; Get the century.
	jmk	bcd2bin
	store 	A,century
	halt
	load	A,INPUT			; Get the year.
	jmk	bcd2bin
	store	A,year
	halt
	load	A,INPUT			; Get the month.
	jmk	bcd2bin
	sub	A,1			; Convert from 1 based to 0 based.
	store	A,month
	halt
	load	A,INPUT			; Get the day.
	jmk	bcd2bin
	store	A,day
	load	A,0b10000000		; Setup the rotation pattern.
	store	A,rotate
; 
; All the inputs should be in place. Start the conversion.
;
	load 	A,year			; Get the year.
	sft	A,R,2			; Divide by 4.
	store	A,B			; Save to B the working result.
	add	B,day			; Add the day of the month.
	load	X,month			; Use X as index into the month keys.
	add	B,monkeys+X		; Add the month key.
	jmk	leapyr			; Returns a leap year offset in A if applicable.
	jmk	working			; Working...
	sub	B,A			; Subtract the leap year offset.
	jmk     cencode			; Returns a century code in A if applicable.
	jmk	working			; Working...
	add 	B,A			; Add the century code.
	add	B,Year			; Add the year input to the working result.
chkrem	load	A,B			; Find the remainder when B is divided by 7.
	and	A,0b11111000		; Is B > 7?
	jmp	A,EQ,isseven		; No then B is 7 or less.
	sub	B,7			; Yes then reduce B by 7.
	jmk	working			; Working...
	jmp	chkrem			; Check again for remainder.
isseven load		A,B		; Is B = 7?
	sub	A,7			; Subtract 7 from B value.
	jmp	A,LT,gotday		; No B is less than 7.
	load	B,0			; Set B to zero because evenly divisible.
gotday	load	X,B			; B holds the resulting day number.	Use as index.
	load	A,sat+X			; Convert to a day lamp.
	store	A,OUTPUT
	halt
error	load	A,0xff			; Exit with error
	store	A,OUTPUT		; All lamps lit.
	halt

;
; Store inputs.
;	
century db
year	db
month	db	
day	db

;
; Static table to hold month keys.
;
monkeys	1				
	4
	4
	0
	2
	5
	0
	3
	6
	1
	4
	6

;
; Need to preserve A while performing some steps.
;
saveA	db	

;
; Subroutine to blink the lamps to indicate working.
; 
rotate	db				; Pattern to rotate.
working	db				; Save space for return adderess.
	store	A,saveA			; Remember the value in A.	
	load	A,rotate		; Get the rotate pattern.
	store	A,OUTPUT		; Show the rotated pattern.
	rot	A,R,1			; Rotate the pattern.
	store   A,rotate		; Save the new rotation.
	load	A,saveA			; Restore the value of A.
	jmp	(working)		; Return to caller.

	org	133			; Skip over registers.

;
; Subroutine takes a BCD nuber in A as input and returns the equivalent binary number 
; also in A.
; 	
bcd2bin db				; Save space for return address.	
	store	A,X 			; Save A.
	sft 	A,R,4			; Get the 10's digit.
	jmk	chkdig			; Make sure digit is 0 - 9.
	store	A,B			; B will hold the 10's digit x 10 result
	add	B,B			; B now X 2
	sft	A,L,3			; A is now 10's digit X 8
	add	B,A			; B now 10's digit X 10
	store 	X,A			; Retrieve original value of A
	and	A,0b00001111		; Get the 1's digit value in binary.
	jmk	chkdig			; Make sure digit is 0 - 9.
	add	A,B			; Add the 10's digit value in binary.
	jmp	(bcd2bin)		; A now has the converted BCD value.

;
; Subroutine determines if the date is a leap year in January or February and returns
; an offset of 1 if it is, and 0 otherwise.
;
leapyr  db				; Save space for return address.	
	load 	A,month			; Check to see if month is January or February.
	and	A,0b11111110		; Are any bits other than bit 0 set?
	jmp	A,NE,notlpyr		; Yes then not January or February. Return 0.
	load	A,year			; Is this an even century?
	jmp	A,NE,chkyear		; No then have to check the year.
	load	A,century		; Yes so see if century evenly divisible by 4.
	and	A,0b00000011		; Are bits 1 or 0 set?
	jmp	A,EQ,islpyr		; Yes evenly divisible by 4 and is a leap year.
	jmp	notlpyr			; No this is not a leap year.
chkyear load		A,year		; See if rear evenly divisible by 4.	
	and	A,0b00000011		; Are bits 1 or 0 set?
	jmp	A,NE,notlpyr		; Yes so not evenly divisible by 4 and not a leap year.
islpyr  load		A,1		; Offset 1.
	jmp	(leapyr)		; Return offset.	
notlpyr	load	A,0			; Offset 0.
	jmp	(leapyr)		; Return offset.		

;
; Subroutine determines if a century code needs to be applied to the calculation.
;
cencode db				; Save space for return address.
	load	A,century		; Century must be between 17 - 20.
chkmin	sub	A,17			; Is century less than 17?
	jmp	A,GE,chkmax		; Yes so century >= 17. Check max boundry.
	load	A,century		; Increase century by 4.
	add	A,4
	store   A,century
	jmp	chkmin
chkmax	load	A,century		; Century must be between 17 - 20.
	sub	A,20			; Is century greater than 20?
	jmp	A,LT,retcode		; No so calculate century code.
	jmp	A,EQ,retcode
	load	A,century		; Decrease century by 4.
	sub	A,4
	store 	A,century
	jmp 	chkmax+2
retcode load 		X,century	; Calculate the century code
	sub	X,17			; Create an index into the century codes.			
	load 	A,ctcodes+X		; Get the appropriate century code.
	jmp	(cencode)		; Return century code.			
;
; Subroutine that checks if the digit passed in A is in range 0 - 9.
;
chkdig	db				; Save space for return adderess.
	store	A,saveA			; Remember value in A.
	load	A,9			
	sub	A,saveA			; Subtract value passed from 9.
	and	A,0b10000000		; Is negative bit set?
	jmp	A,NE,error		; Yes so value in A not in range 0 - 9.
	load	A,saveA			; No so A value in range. 
	jmp	(chkdig)		; Return to caller.

;
; Static table to hold the output pattern for the day of the week. 
;
sat     0b00000010
sun     0b10000000
mon     0b01000000
tues    0b00100000
wed     0b00010000
thur    0b00001000
fri     0b00000100

;
; Static table to hold century codes.
;
ctcodes 4
	2
	0
	6

While there is a check to make sure that the BCD inputs only contain the digits 0-9, some additional checks for the month (1-12) and day (1-31) were not implemented because I ran out of memory space. The above program takes up 251 of the 255 bytes of available memory. So while the instruction set is more than adequate for most tasks, the limiting factor for doing interesting things on this machine is memory.

The updated IDE and this example have been added to the GitHub for this project.

Discussions