x86 real mode

It seems the "safest" (most conservative) assumption is to figure a single 64KB address space. If, instead, the x86 ALWAYS prepared a separate data space (etc), then I could assume a larger model.

Or, if the tiny model was IMPRACTICAL for any use (and was just included as an homage to the 8085).

Or, if ints were always 32b, etc.

I.e., it seems safe to assume the tiny model was intended to be

*usable* and not just "an engineering/marketing exercise".

OK. Point of my question being the oddities of the x86 architecture aren't (necessarily) exposed in the software. I need to come up with a solution that is readily portable to non-x86 architectures.

Understood. I recall the 4KB object limitations that the Z180 presented. This shouldn't be hard to accommodate (if considered AHEAD OF TIME and not shoehorned into the design after-the-fact).

(actually, I think I can probably leverage some of my more ancient codebases if I can locate them :-/ I hadn't considered that possibility!)

Thanks!

--don

Reply to
Don Y
Loading thread data ...

(snip)

Well, the 8086 was an upgrade from the 8080 or 8085. They knew by then that people were running out of address space. In 1976, I used an 8080 system with 64K unmarked (factory samples, or something like that) DRAM.

Systems with (externally) bank switched RAM weren't all that unusual, so maybe putting something like bank switch inside the chip wouldn't have been so strange.

But yes, I am pretty sure that Intel didn't expect the 80286 to mostly be used in systems only running real mode.

I believe that kludge is still there today. Also, the A20 address line switch.

Also, the original IBM/PC used a 14.31818MHz crystal, convenient for generating NTSC video with the CGA. That signal went on the bus for that reason. The processor then ran on that frequency divided by three (that is the way the clock generator works).

Now, all ISA based PCs need the 14.31818 MHz crystal to supply that line, just in case someone uses a CGA. A few times I put a CGA in my AT clone to generate test patterns for TV alignment.

-- glen

Reply to
glen herrmannsfeldt

*I* haven't seen the code, either. Please note the wording of my initial post: "What are the most conservative, *practical* expectations I can make living in x86 real mode:"

expectation: a strong belief that something will happen or be the case in the future

Had I a *choice* in defining the implementation, I would have asked what POSSIBILITIES were available to me in making that choice. *THAT* is a question to which I can easily get an answer without relying on folks' "experiences".

Reply to
Don Y

(snip)

Seems like a compiler bug to me. If you assign to a far pointer, then it is your fault.

You don't say what language you use, but C only allows comparison of pointers to different objects as equal or not equal. With large model, objects (arrays and structs) can't get larger than 64K. (For C, they can't even equal 64K.) A C compiler should generate properly comparable (for equal or not equal) pointers in large model.

In many cases, I have used 2D arrays, actual C arrays of pointers to arrays. As long as the array of pointers and pointed-to arrays fit in 64K, you can get plenty big enough.

I believe in the OS/2 1.x days, I had 16M on my system before converting to OS/2 2.x and appropriate compilers.

I could generate arrays to at least 1 million elements of double, so about 1000x1000, which easily fits.

As long as you stay within small or large, it isn't all that hard. It is mixed models (using a few far pointers in small model) that gets confusing. Go completely to large, never use the far keyword, and everything works fine.

There is medium and compact, and I forget which is which.

I usually generate libraries for small and large, and only used those. (When you install the MS C compiler, you select which models you plan to use, and it generates the appropriate libraries.)

-- glen

Reply to
glen herrmannsfeldt

The 8085 was essentially an 8080 with an internal clock driver (and a couple of other little fixups -- RIM/SIM, INT5.5/6.5/7.5, etc.)

Yes, an Our Hero, Bill figured 640K was more than anyone would EVER need! :-/

[How much nicer things would have been had the 68K won that design-in]

I can recall getting 16Kx1's on an 8085 and DROOLING over "all the space"!

This is, in fact, what the Z180/64180 devices did for the Z80 (though in a really kludgy fashion).

TI (?) also had some tonka toy parts that did this (for those of us who didn't want to live with 74189's serving that function).

But, the 286 powered *up* in real mode. Which, no doubt, is why real mode persisted (persists?).

IIRC, the original "ISA" bus wasn't even formally characterized until after the fact.

Reply to
Don Y

Am 17.10.2014 um 23:32 schrieb Don Y:

Huh? How do you figure that the possibility that causes your own code to make _the_most_ assumptions about the behaviour of unknown, surrounding code, to be the _safest_? When did it become safe to make unwarranted assumptions?

Well, yes, it essentially always does just that. All code addressing is relative to CS, all data addressing is relative to DS or SS, at least by default. And since all those are allowed to be different, the only a-priori _safe_ assumption obviously is that they will be.

It's practical if the program is small enough to allow it, and the program loader being used supports it.

The usual one at the time did: tiny model is what MS-DOS *.com format programs use. They're copied verbatim from disk to RAM (beginning 0x100 bytes into a 64 KB page) , then all three primary segment registers are set to the start of that RAM region (i.e. CS=DS=SS=constant) and the loader just jumps to CS::0x0100. So code and pre-initialized data are in one big block of RAM. The remainder of those 64K are assumed to be used for .bss and stack.

The advantage was that the program loader doesn't have to make any code modifications to handle relocation of the code ... all addresses in the code are just offsets relative to the load base, which itself doesn't ever appear in the code, because it's kept in the segment registers.

The disadvantage was that the loader _didn't_ perform relocations, so there was no way to load bigger programs. So along came the EXE program file format, which is a whole different kettle of fish.

It stopped being truly usable when programs outgrew 64 KB for code and all(!) data combined.

Reply to
Hans-Bernhard Bröker

(snip, I wrote)

And a 5V only power supply. As well as I know it, the 8085 and Z80 are but successors to the 8080, but different groups from intel. (One group left, one stayed.) Being 5V only made it a lot easier to use in small systems.

Might have been a 68008, though. They seem to like the 8 bit bus.

The IBM/370 powers up with DAT off. You have to have some way to initialize the tables you need before you start using them.

I was recently reading "The Intel Trinity" which probably says when they started working on the design of the 286.

The kludge was needed for OS/2 1.x to run DOS box code. IBM figured that out in time to get it into the PC/AT.

IBM always was, and still is, good at documented what they did, though not always why they did it. There are manuals that give lots of detail on every line of the bus, and BIOS listings with comments.

But then there were mistakes along the way. Edge triggered interrupts make it hard to share INT lines.

-- glen

Reply to
glen herrmannsfeldt

Am 18.10.2014 um 00:00 schrieb glen herrmannsfeldt:

Since there was no formal definition other than the individual compiler's documentation of what a "far" pointer did and did not to in various situations, the only way it could be a bug would have been if it didn't match its own documentation.

Since neither "far" nor "huge" pointers even exist in the language standard, none of its rules apply to them unless the compiler authors decide to say so.

They decided not to. I actually had to grow some data structures' sizes to the next power-of-two so a huge pointer could correctly walk a >64KiB array of it. The horror!

Reply to
Hans-Bernhard Bröker

(snip)

Not so unusual to keep DS and SS together.

Also, even for the 8080 you could have separate address space for stack and data. There was a way to decode which data references were to the stack. I don't know anyone ever did that, though.

Well, pretty much they did it the same way as CP/M, which allows for easy porting.

The descriptions I remember for COM didn't say that you were stuck with 64K, but that it was the program's responsibility to keep track of everything. You had all the rest of memory to use, COMMAND.COM would be reloaded from disk if you wrote over it.

-- glen

Reply to
glen herrmannsfeldt

Well, it has to match the standard that the compiler claims.

If you aren't using mixed models, the compiler has to consistently support the model in use. (That is, no near or far keyword.)

But most probably didn't want to go all the way to huge, as it would slow down (and increase code size) for all pointers.

Mixed model, it is your responsibility to get it right.

The C standard allows for a variety of pointer models, including those of large and huge model. It doesn't allow for the "far" and "huge" keyword.

-- glen

Reply to
glen herrmannsfeldt

Note that most practical memory still required two (even *three*!) power supplies.

Different approaches to the "problem" (that being, the design of an 8 bit MPU). Likewise, the Motogorilla/NatSemi/Signetics/TI/etc. folks each took a trip in a different direction. We seem to have been left with the worst of the bunch :-/

(Well, perhaps not *worst* -- there were some really ghastly designs in the early/mid 70's. But, it beat the hell out of coding for the i4004!)

The Z80 was a much more powerful architecture than the 8080/85. Especially if you were trying to deal with quick response times for ISRs, structured coding, etc. Far more suited to HLL constructs (though still very clumsy).

Most of the "16b" machines were dog slow, by comparison. And, ate more memory on top of it!

But, there was a SEEMLESS path upward from the 68000. Intel gave us all these "backward compatibility issues" to carry forward (into the 31st century!). It was just a memory bandwidth tradeoff (08, 010, 020, etc.) moving forward.

I think documentation exists *now* but not "back then". I can recall having one helluva time trying to design "ISA bus" cards with any sort of *guarantee* that they would work in ANY pc (even genuine big blue). You ended up designing empirically and to "typical" numbers as there were no hard and fast numbers that you could rely upon from a PC vendor.

Reply to
Don Y

For an 8086? I'm pretty sure it was all 100% "real mode".

--
Les Cargill
Reply to
Les Cargill

No, actually it won't - at least not in the "large" model with Intel or Micro$oft compilers.

Far pointers are constructed by left shifting the segment value by 4 bits and adding a 16 bit offset - i.e. they are a 20 bit address expressed as 4:16. In real mode there are no reserved segment values so there are 4096 possible far pointers for *every* location in memory (with wraparound arithmetic there is a possible segment base every

16-bytes within -32K..+32K of the location).

Huge pointers are 16:4, the 20 bit address normalized to a 16 bit segment and a 4 bit offset. This ensures that every distinct huge pointer value references a unique location in the 1MB space.

The problem is that normalization is costly (a few extra instructions) and compilers don't do it unless you explicitly ask for it by using the huge model or by using huge pointers in other models.

OS/2 1.x operated in 80286 protected mode, which has other issues that are not relevant to Don's question.

That said, you are absolutely correct that there are ways to get around the limitations of 16-bit segments.

Yeah, I don't recall which is which just now, but one is 64K code +

1MB data and the other is the reverse.

The "large" model allows 1MB code+data using unnormalized pointers. The "huge" mode is the same but using normalized pointers.

George

Reply to
George Neuner

Definitely a compiler bug which was fixed eventually - but I can't remember which compilers and versions had the bug. I do know they all were Micro$quish compilers ... I don't recall every having the problem with an Intel compiler.

The problem occurred when using a huge pointer with an index that took it very close to a 64K step: the intermediate pointer would be correct so far as the location it addressed, but would be a 4:16 unnormalized pointer rather than a normalized 16:4 pointer. If the value in memory straddled the 64K boundary, the fetch or store would wrap around to the beginning of the segment and the result would be incorrect (and often very hard to find).

George

Reply to
George Neuner

Hi Don,

Billy boy had nothing to do with it - he wasn't even in the picture when the PC was designed. IBM wanted CPM-86 for the PC and only turned to Microsoft when DR made a mistake in dealing with them.

IBM wanted to keep the PC "cheap" and that meant an 8-bit bus. At the time the 8088 was available but the 68008 was still in pre-production and Motorola couldn't guarantee delivery in quantity by IBM's deadline. So the 8088 won by de fault of Motorola not having an 8-bit part.

True.

George

Reply to
George Neuner

(snip, someone wrote)

(then I wrote)

Yes, but the compiler should make sure that only one of those is ever used. That is, unless you cheat and change them yourself.

I tried to avoid huge, but yes.

Actually, no. I am pretty sure that at least MSC 6.x, which is what I used at the time, generated segment selectors 4K apart and

16 bit offsets. The other way doesn't work right with OS/2. You don't want to allocates a segment selector for every 16 bytes.

The compilers ran under either DOS or OS/2, and generated the same code in either case. I even used to generate bound executables which run under either DOS or OS/2.

But in any case, it is the responsibility of the compiler to make it work, if you follow the C standard. Normalizing them yourself isn't standard C. Casting a huge pointer to a far pointer isn't, either.

I don't think I ever tried arrays of struct of unusual size. It might have padded them out. I did use large model pretty much all the time, and never had any problems.

And I pretty much never used it. But a 4K entry array of pointers to 8K doubles allows for 32MB which was big for a 286.

Actually, large allows for a lot more than 1MB. And OS/2 will swap whole segments to/from disk, so you can do really large arrays.

OS/2 1.x allowed for 8K segment selectors for a user process, each could be up to 64K, so 512MB. With DOS, only 640K, or if you use the right display board, you can go up a litle more.

I started OS/2 1.0 in 1990, and tried not to use DOS after that.

-- glen

Reply to
glen herrmannsfeldt

As the memory models persisted in 16-bit protected mode, it's more correct to say that medium model supported one code segment and multiple data segments, and compact the other way around. And theoretically you could have implemented segmentation and memory models in 32-bit protected mode as well, but that was quite rare (I think WATCOM's C compiler actually supported that), and almost all

32-bit (and 64-bit) protected mode programs effectively run in tiny model.
Reply to
Robert Wessel

Didn't mean to imply that he had a hand in the design of the hardware. Only that he had "concluded" that 640K was effectively "infinite" and, as such, cast the foundations of MS-DOS in mud instead of "thinking forward".

Sort of like picking Jan 1 1970 as the epoch ("Obviously, time will come to an end before die...")

Always amusing to see lack of vision, in practice! :>

Reply to
Don Y

IIRC, the usual scheme for huge mode support was to always use 64KB segments, and then tweak the segment arithmetic to match the tiling. So for real mode, consecutive segments for a huge object were 4096 apart. In PM, the OS allocated consecutive selectors, which were numerically 8 apart. So an 80KB huge object might be in segments

0x1234 and 0x2234 in real mode, and in selectors 0x4567 and 0x456F in protected mode. The environment provided some run-time readable variables to define the increment (4096 vs. 8) and corresponding shift counts (12 and 3).
Reply to
Robert Wessel

(snip, somewone wrote)

(then I wrote)

MS used the same compiler for DOS and OS/2. You could even generate bound executables that would run on either, so it isn't so easy to change the addressing system for different targets. So, there was at least one that would generate selectors 4K apart. That does have the disadvantage that you can only go to 16 segments.

But they might have done both at different times. I mostly remember the MSC 6.0 compiler.

-- glen

Reply to
glen herrmannsfeldt

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.