I've edited my reply to remove the hardware-oriented parts since lidnariq et al can answer those. I did do write-ups but this thread is turning into what I call a "time vampire" and that isn't making me happy.
rchoudhary wrote:So based on that, let's say my main.asm file had something like this:
{snipping for brevity}
Would the preprocessor turn lda myvar into lda $008100? Can the assembler handle a full 24 bit address though?
There's no preprocessor involved. I think you mean "assembler pass", which would be true -- sort of. It's more complicated with assembler+linker combos but I'll explain it.
I can't answer the first question because I don't know what WLA DX does with
.bank 0. I don't use WLA DX for reasons exactly like this, and exactly why I chastise the thing to no end. I would expect to see an
.org in there somewhere, or possibly a linker config that specifies bank-to-address values.
.bank 0 to me doesn't smell like "65816 bank $00", it smells more like "the first bank". There must be something else that correlates that with an actual 65816 bank/address.
As for your 2nd question: of course the assembler can handle long addressing! Nothing stops you from doing
lda.l myvar to use opcode $AF (
lda abslong), which is 4 total bytes in size.
But
what address it assembles to is going to depend on WLA DX's configuration (either the assembler or the linker, it depends. "It depends" applies to ca65/ld65 too, just for the record. Linkers make something that's pretty logical/simple into something much more complicated.) This is why, again, I strongly suggest generating listings files. From these you can often discern if the address you expect matches what the assembler generated.
For symbols that get deferred to the linker, the situation is more complicated, as the addresses are "relative" or "relocatable" rather than literal. I think it's easier if I just demonstrate using ca65/ld65 (because there's no way in hell I'm touching WLA DX). Below is a line of code I wrote, and this output is from the listings file ca65 (the assembler) generated:
Code: Select all
00038Ar 1 BF rr rr rr lda f:TextLookupTable,x
This is an accumulator load from a 24-bit address indexed with X. The address is label called
TextLookupTable. I forced 24-bit addressing using the
f: prefix (this is akin to WLA DX's
.l suffix on the opcode, or on the operand). But you can see there's no actual address there -- it shows
rr rr rr where there would normally be literal values (example: had I written
lda $7e1234,x I would've gotten
BF 34 12 7E for the actual bytes in the listing file itself). So I have to refer to the symbol-to-addressing map file that the linker (ld65) generates. Here we find it:
And there we have our answer: address $039800. Thus, we can safely assume the full instruction is
BF 00 98 03 -- and sure enough that is what's in the ROM file.
Because you're asking all sorts of questions, the next thing you're going to ask me is how exactly the linker knew to put that variable in bank $03 address $9800? Those details come from a combination of the actual code itself (where
TextLookupTable is declared) and the linker configuration. So let's look at that. First the code:
Code: Select all
.segment "BANK03" : far
...
TextLookupTable:
.repeat 12, row
.repeat 8, i
.word ((i*2)+(row*$20) & $03FF)
.endrepeat
.endrepeat
...
The important part is on the first line: this variable/label is inside of a segment called
BANK03, and it's declared as
far (which means the assembler is supposed to know intelligently when to use 24-bit vs. 16-bit addressing, but I wanted to take no chances). The important thing is that it's not in bank $00 where my code runs from. So what about this magic
BANK03 thing? On to the linker configuration:
Code: Select all
MEMORY {
...
ROM00: start = $008000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
ROM01: start = $018000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
ROM02: start = $028000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
ROM03: start = $038000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
...
}
SEGMENTS {
CODE: load = ROM00, align = $8000, type = ro, optional = no;
BANK01: load = ROM01, align = $8000, type = ro, optional = yes;
BANK02: load = ROM02, align = $8000, type = ro, optional = yes;
BANK03: load = ROM03, align = $8000, type = ro, optional = yes;
...
}
This is where I start getting annoyed at ld65 (I'm one of those people who prefer assemblers without linkers, i.e. I'd rather just use
.org statements), but hopefully you can "sort of" piece together what's happening there: segment BANK03 refers to a memory layout entry called ROM03. ROM03's memory layout says the start of the segment is at $038000 in 65816 addressing space. All this correlates/works properly with mode 20 given its memory mapping.
Remember: the linker is what generates the ROM file itself, which is how it's able to "fill in all the blanks" that the assembler couldn't.
I am certain WLA DX works similarly in this regard, I just want nothing to do with its terrible syntactical sugar to try and make heads/tails of it. It doesn't really make any sense to me either, while ca65/ld65's setup at least makes MORE sense, once you put all the pieces together.
rchoudhary wrote:User 93143 pointed out that there is a Data Bank Register. Would the assembler use that instead? Like would it load the Data Bank Register with $00 and then just do lda $8100?
No, the assembler will not do this for you automatically. You can do it with code yourself. Again: I don't speak WLA DX but this can be done with pretty much any assembler using pseudo-ops that allow you to get the bank address of "something" (a symbol, label, whatever) and do whatever you want with it. Using my above ca65/ld65 example, I could effectively do this in my code:
Code: Select all
sep #$20 ; A=8
lda #.bankbyte(TextLookupTable) ; or even .bankbyte(BANK03)
pha
plb
You might know
.bankbyte as
^ in WLA DX or other assemblers -- it gets the upper 8-bits of the 24-bit address (calculated either at assemble-time or link-time; see above).
There is currently no assembler out there, that I know of, which "tracks" B (a.k.a. DB, i.e. data bank register) changes. In fact, you can't do this reliably anyway in the assembler, at least not reliably 100% of the time, for the exact same reasons as why you can't reliably 100% of the time track
sep/rep sizing: because those instructions happen at runtime, not assemble-time. An assembler is not an emulator. The best the assemblers can do is try to "follow your code" (linearly) and handle it from there. This is why every assembler has pseudo-ops that let you tell it "8-bit X/Y" or "16-bit A", etc.. Just nobody has done it with B/DB.
There is an assembler in the works that will handle D (direct page) and B/DB changes through pseudo-ops, but it's not out yet and still being worked on (but actively/daily).
The rule of thumb is this: if you change S (stack), D (direct page), or B/DB (data bank register), it is _your responsibility_ to make sure your code does the right thing and is accessing the right variables, behaves correctly, backs up/restores those values (if doing something temporary), etc.. The assembler cannot do everything for you magically.
rchoudhary wrote:And then finally, in
the wikibook article on memory mapping, there is this chart:
{snipping for brevity}
The part that confuses me is the hardware registers bit. If I want to force blank, I simply write $80 to $2100 in the code. But in which bank? What's the difference between $002100, $002100, ..., $3F2100? Also, why can I just do a
lda $2100 instead of having to do
lda $xx2100 (where xx is whatever the right bank is? I'm assuming it's because of the Data Bank Register?
I think these massive posts and walls of text (replies, questions, everything) are causing you to miss/overlook things already stated.
There is no difference in mode 20 between $2100 at bank $00, bank $01, or bank $3E. The $2000-5FFF region is mirrored as well.
PLEASE STOP LOOKING AT THAT TERRIBLE MEMORY MAP ON THE WIKIBOOKS WIKI. Instead, look at Tepples' mode 20 memory map again:
download/file.php?id=15008 :-)
Address ranges $2000-5FFF are labelled
I/O for a reason (MMIO registers). Now look at the bank numbers at the top of the picture. Now you understand the mirroring; i.e. you can have B set to $2E and do
sta $2100 and you'll affect the screen brightness register. This is why I said earlier mode 20 is "easy to program for"!
I stated earlier that the 65816 on power-on/reset starts in bank $00 -- that's where your code starts executing from. B is also set to $00. The 65816 CPU starts in 6502 emulation mode as well. This is why one of the most common set of instructions at a RESET vector are these:
Code: Select all
sei ; Inhibit interrupts
phk
plb ; Sets B/DB to the same bank as K (active code bank)
clc
xce ; Enable native 65816 mode (i.e. get out of emulation mode)
Often followed by this, or a variation of:
Code: Select all
rep #$30
lda #$0000
tcd
ldx #$01FF
txs
And a
cli later on to re-enable interrupts (this doesn't affect NMI, only IRQ and some others).
BTW, you cannot
lda $2100. Not all MMIO registers are readable -- in fact, it's best to assume they aren't unless explicitly stated in documentation.
Try referring to this. Because of this fact (not all MMIO registers are readable), it's up to you to retain MMIO register contents through use of a separate variable somewhere in direct page or RAM, if you need to. It's based entirely on circumstance/situation. $2100 is not a great example because it pretty much just does forced blanking (screen off) and brightness. But if you look at $2101 (also not readable), you can see how there would be situations where you may want to tweak a single bit in that MMIO register, but since you can't read it directly, you need to "keep track" somehow...