• Please review our updated Terms and Rules here

The running PDP-8/SBC6120 software project/questions thread

Huh, I was under the impression that the MQ instructions were part of the EAE package going back to the straight-8. Need to have another run over the model list...
 
Huh, I was under the impression that the MQ instructions were part of the EAE package going back to the straight-8. Need to have another run over the model list...

No, you're right. If you have the Type 182 Extended Arithmetic Element for your straight 8, then you do have the MQ as well.
BSW only came with the 8/E though.
 
Ah.

On the subject of model differences, I'm curious what combining IAC and rotate on unsupported models actually does? Is it simply a different behavior (rotate-then-increment vs. increment-then-rotate,) or is it unpredictable? Every source mentions that it's unsupported, but nobody seems to say specifically what happens.
 
On the subject of model differences, I'm curious what combining IAC and rotate on unsupported models actually does? Is it simply a different behavior (rotate-then-increment vs. increment-then-rotate,) or is it unpredictable? Every source mentions that it's unsupported, but nobody seems to say specifically what happens.

Looking at schematic pages D-8P-0-2 and D-8P-0-3, my interpretation is that on the straight-8 the rotates will decode into pulses for each of RAR, RAL, RTR, and RTL. These will trigger drivers to attempt to jam a result into the AC latches. At the same time, the adder will generate a result which will be fed to the count inputs.

I'd have to look at the internal structure of the AC register bits to confirm, but I suspect the result is that the count pulses get over-ridden, and you just get the shift.

Vince
 
Huh, interesting.

Apparently wrong, too.

A look under the hood of the R210 bit slice shows all the rotate inputs to be clock inputs. (More properly, pulsed DCD inputs.) The half adder input appears to be level based, which makes me think that might win instead.

Any straight-8 owners able to experiment?

Vince
 
Ah.

On the subject of model differences, I'm curious what combining IAC and rotate on unsupported models actually does? Is it simply a different behavior (rotate-then-increment vs. increment-then-rotate,) or is it unpredictable? Every source mentions that it's unsupported, but nobody seems to say specifically what happens.

Documentation isn't helpful. The manual states at which time (phase) the different operations happens, and there it is stated that the IAC and the rotates happen at the same time, which is (I assume) why you cannot combine them. But what the actual result is I have no clue.
The original and 8/S does the OPR instruction in three time states. Later models use 4 to be able to separate the IAC from the rotates.
 
Got a little actual coding done today - a first attempt at a multiply routine (unsigned,) for systems without hardware multiply. Russian-peasant is slow but simple, and since the aim here is more about saving space than being fast, I think it's preferable to fancier, faster methods that'd require a table. I ended up doing it reversed compared to the conventional method (leave the addend in place and shift the sum left, rather than leaving the sum in place and shifting the addend right) because it required less juggling that way (though any port for 8s that don't support SWP is going to be doing a bunch of juggling in any case...) And I couldn't find a good way to early-out without making the loop significantly slower on every pass prior to early-out...

Still need to test this, but I might as well throw it up for comment:
Code:
c7764,	7764
domul,	/ DOMUL - multiply #0 by #1
	mql		/ save #0
	tad i z psp	/ get #1
	dca z tmp	/ save it in ZP for speed
	tad c7764	/ load AC with -12
	dca z tmp2	/ save it for a loop counter
mloop,	cll ral		/ shift the sum left
	swp		/ retrieve #0
	cll ral		/ shift #0 left
	swp		/ retrieve the sum
	szl		/ if link is set,
	tad z tmp	/ add #1
	isz z tmp2	/ increment the loop counter
	jmp mloop	/ if we didn't skip, continue
	isz z psp	/ increment the stack pointer
	jmp i z ip	/ 15 instructions, 16 words
 
Apparently wrong, too.

A look under the hood of the R210 bit slice shows all the rotate inputs to be clock inputs. (More properly, pulsed DCD inputs.) The half adder input appears to be level based, which makes me think that might win instead.

Any straight-8 owners able to experiment?

Vince

Sorry I missed this discussion in April.

I spent half an hour warming the old girl up and verified basic operation enough to punch in the following four codes.

CLA CLL IAC RAL 7305 which should give L=0 AC=0002 and gives 0 0000
CLA CLL IAC RAR 7311 which should give L=1 AC=0000 and gives 0 0000
CLA CLL IAC RTL 7307 which should give L=0 AC=0004 and gives 0 0000
CLA CLL IAC RTR 7313 which should give L=0 AC=4000 and gives 0 0000

It looks to me like the IAC never happens. I then tried making this two instructions in order to verify the results I thought should happen and got the expected results. Of course this does not mean that in all cases the increment ends up stifled but testing all cases is more than I want to do right now. After warming up half an hour it still would not boot OS/8 from DECtape. Something is still not working correctly. My basement is now a couple of degrees warmer and it is going to be hot today. I will wait until I need the heat to work on it.

If you have any specific cases you want me to test I will do that.

Doug
 
Sorry I missed this discussion in April.

I spent half an hour warming the old girl up and verified basic operation enough to punch in the following four codes.

CLA CLL IAC RAL 7305 which should give L=0 AC=0002 and gives 0 0000
CLA CLL IAC RAR 7311 which should give L=1 AC=0000 and gives 0 0000
CLA CLL IAC RTL 7307 which should give L=0 AC=0004 and gives 0 0000
CLA CLL IAC RTR 7313 which should give L=0 AC=4000 and gives 0 0000

It looks to me like the IAC never happens. I then tried making this two instructions in order to verify the results I thought should happen and got the expected results. Of course this does not mean that in all cases the increment ends up stifled but testing all cases is more than I want to do right now. After warming up half an hour it still would not boot OS/8 from DECtape. Something is still not working correctly. My basement is now a couple of degrees warmer and it is going to be hot today. I will wait until I need the heat to work on it.

If you have any specific cases you want me to test I will do that.

Doug

As mentioned above, according to documentation, you cannot have IAC in combination with rotates, as they happen at the same clock phase.
Seems the documentation is correct then. :)
And I guess, from your information, that the IAC just is "ignored" in that situation.
 
I was reading through your code and your implementation of inclusive or can be improved if you assume you have the MQA and MQL instructions. This is because MQA is an inclusive or.

Code:
door,   .-.             / DOOR - OR #0 and #1
        MQL             / MQ <- #0
        tad i z psp     / get #1
        MQA             / do the OR
        isz z psp       / increment the stack pointer
        jmp i door      / return

Doug
 
Yeah, I realized that one a while back while going back and re-factoring things to provide some configurability on the portability-sensitive bits. I've now got almost everything set up so that PALBART's conditional-assembly features will allow the interpreter to be built for most if not all models of -8, taking advantage of the niceties when they're available and doing things the hard way transparently when they're not.
 
As mentioned above, according to documentation, you cannot have IAC in combination with rotates, as they happen at the same clock phase.
Seems the documentation is correct then. :)
And I guess, from your information, that the IAC just is "ignored" in that situation.

I've known this since 1975 or 76 when I was programming the Straight 8 using the 1972 small computer handbook for the 8/e and the combination for IAC and RAL didn't work. The 1972 version was the one sold in the campus bookstore. I just never bothered to figure out what it did instead until now. And at the moment I have only a partial understanding. It may be that the IAC portion is always ignored but there could be situations where it might do something else. And it could behave differently on different machines at different points in the life cycles of their transistors. None of that really matters because I would be foolish to try to depend on an undocumented feature that just happened to do something I needed. It is just an interesting oddity.

Doug
 
Code:
doskn,	/ DOSKN - skip (IP += 2) if #0 is negative
	0
	spa cla		/ if #0 is not negative,
	jmp sn		/ skip skipping
	isz doskn	/ otherwise, skip
	isz doskn
sn,	tad i z psp	/ get the new #0
	isz z psp	/ increment the stack pointer
	jmp i doskn	/ 7 instructions, 8 words
	/ HD6120: 47* cycles, 8/e: 15* us, 8: 19.5* us
	/ * 61 cycles/19 us/22.5 us if skip taken

I was thinking about your double increment of the return address. I am guessing your intention here is to code the call something like:

Code:
        JMS DOSKN       /SKIP IF NEGATIVE?
        JMP I .+1       /RETURNS HERE IF NEGATIVE
        ITSNEG          /DESTINATION POINTER
/POSITIVE TOP OF STACK RETURNS CONTROL HERE


/AND SOMEWHERE OUT HERE WE NEED TO PROCESS THE NEGATIVE CASE.
ITSNEG,                 /COMES HERE IN THE NEGATIVE CASE.

Where the two word hole is intended to allow for a JMP indirect allowing you to ignore page boundaries. There is a situation where this doesn't work and that is if the JMP I .+1 happens to be stored in the last word on a page. The pointer will be in the first word on the next page and not reachable by the JMP I .+1. PAL will issue an error for this when it happens. You could instead just have the routine transfer control to the single word indirect address. Code the call like this:

Code:
        JMS DOSKN       /SKIP IF NEGATIVE?
        ITSNEG          /DESTINATION POINTER
/POSITIVE TOP OF STACK RETURNS HERE

And the routine would change to this.
Code:
doskn,	/ DOSKN - jmp to arg or skip (IP += 1) if #0 is negative
	0
	spa cla		/ if #0 is not negative,
	jmp sn		/ skip skipping
	tad i doskn	/ grab the new return address
	dca doskn 	/ replace the original
sn,	tad i z psp	/ get the new #0
	isz z psp	/ increment the stack pointer
	jmp i doskn	/ 7 instructions, 8 words

Still 7 instructions but one extra defer in the case of no skip. However you save a word on every call by eliminating the JMP I which makes it a net gain of both space and time and the page boundary issue goes away completely.

Also, I am assuming you intended doskn to be read as do skip negative but the skip occurs if the top of stack is positive. Or am I confused?
 
Last edited:
Kindof weird either with a two word skip, or having the actual address as the next word. Conventionally, code would only skip one word, and you usually used the assemblers capability of generating literals at the end of the page implicitly.
 
I really do need to get something like an up-to-date code dump in here...but anyway, yeah, the skip instructions are a little odd, but I went with this since I find (as a matter of personal taste/style) it's a little more general-purpose useful than dedicated conditional-branch-to-address instructions. With the conditional-skip, one has the option of either branching to an immediate address (if the conditional clause is longer than two instructions) or reversing the test and doing the conditional action in the skipped space (if it's one or two instructions.) Not as flexible as full conditional execution, granted, but that would require a real decoder or a larger dispatch table. The two-word skip, as Doug intuits, is to allow room for a jump-immediate instruction (rather than having to juggle putting a potential branch address on the stack and then duplicating/swapping the test condition over top of that.)
 
Thing is, the more common way is to do like this:
Code:
        .
        JMS I (ROUTIN    / Call routine
        JMP I (WASNEG   / If AC was negative at call, we end up here.
        .                        / If AC was positive, we end up here.
        .
        .


ROUTIN, 0
        SMA CLA         / Skip if AC is negative, and clear it.
        ISZ  ROUTIN   / If AC was positive, we skip next instruction at caller
        .
        .
        JMP I ROUTIN
 
Thing is, the more common way is to do like this:
Code:
        .
        JMS I (ROUTIN    / Call routine
        JMP I (WASNEG   / If AC was negative at call, we end up here.
        .                        / If AC was positive, we end up here.
        .
        .


ROUTIN, 0
        SMA CLA         / Skip if AC is negative, and clear it.
        ISZ  ROUTIN   / If AC was positive, we skip next instruction at caller
        .
        .
        JMP I ROUTIN

I've never liked using literals. They always seemed to cause more problems down the line when your code runs into the literals at the end of the page. I find it better to just plan ahead and place the pointers in places that don't interfere. They are also very difficult for a compiler to utilize. This is why I started using pointers as arguments. It completely eliminates the issues of off page references and doesn't cost you any space. Basically the pointer takes the place of the JMP I without the need for the space used by the literal.

bqt's suggested implementation of doskn is the prettiest. It looks like this:
Code:
DOSKN,  .-.             /SKIP IF TOP OF STACK (AC) IS NEGATIVE AND THEN MOVE NEXT TO AC
        SPA CLA         /REVERSE THE SENSE TO SKIP THE RETURN ADDRESS BUMP
        ISZ DOSKN       /PERFORM THE SKIP
        TAD I Z PSP     /MOVE NEXT TO THE AC
        ISZ Z PSP       /FIX UP SP
        JMP I DOSKN     /RETURN

This takes 10 cycles if the skip is taken and 8 cycles if no skip. The pretty much required JMP that would be skipped adds an additional 1 cycle if on page or 2 cycles if off page destination.

My suggested implementation earlier in the thread has a bug. Strictly speaking it is not a skip anymore so I call it a branch and the branch is taken if negative. Here is the correct implementation.
Code:
DOBRN,  .-.             /BRANCH IF TOP OF STACK IS NEGATIVE AND MOVE NEXT TO AC
        SMA CLA         /SKIP IF TOP OF STACK IS NEGATIVE
        JMP BRNLB1      /JMP IF TOS IS >=0
        TAD I DOBRN     /FETCH RETURN POINTER
        DCA DOBRN       /WE WILL NOW RETURN TO THE BRANCH ADDRESS
        SKP             /SKIP THE RETURN ADDRESS UPDATE FOR NON BRANCH CASE
BRNLB1, ISZ DOBRN       /RETURN AFTER THE BRANCH POINTER
        TAD I Z PSP     /TOS GETS NEXT
        ISZ Z PSP       /FIX UP SP
        JMP I DOBRN     /RETURN TO THE CORRECT PLACE

This takes 15 cycles if the branch is taken and 11 cycles if not. The branch taken path could be reduced by 1 cycle if some code is duplicated. The big advantage of doing it this way is you never need to worry about off page references and the extra words of code generated by the literal pointers. Overall this will result in smaller programs although there will be a speed penalty.

Doug
 
A bit surprised about the aversion to literals, but to each his own, I guess.
I've written quite a lot of code on the PDP-8 over the years, and I can't say I've ever had issues by literals and code colliding. And the assembler usually do find if you refer to the same literal multiple times, and only have the literal once, which saves some space.

One good reason for not having pointers is that you might not actually want to do a JMP in the first place. One obvious thing is when you want to reverse the check. Then you follow the JMS with a SKP.

And of course, I would write the DOSKN like this:
Code:
DOSKN,  .-.             /SKIP IF TOP OF STACK (AC) IS NEGATIVE AND THEN MOVE NEXT TO AC
        SPA CLA         /REVERSE THE SENSE TO SKIP THE RETURN ADDRESS BUMP
        ISZ DOSKN       /PERFORM THE SKIP
        TAD I PSP       /MOVE NEXT TO THE AC
        ISZ PSP         /FIX UP SP
        JMP I DOSKN     /RETURN

(No reason to have the explicit Z in there. :) )
 
Back
Top