6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 24, 2024 5:19 pm

All times are UTC




Post new topic Reply to topic  [ 76 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next
Author Message
PostPosted: Sat May 14, 2022 6:52 pm 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
I'm trying to code the following memory map for the PLD address decoder for my 65816:
Code:
RAM   $0000-$FEFF
ROM   $FF00-$1FFFF
I/O   $20000-$200FF
EXRAM $20100-$7FFFF

It wouldn't fit within my PLD with the following logic:
Code:
FIELD Address = [A18..A8];
RAM       = Address:[0000..FFFF];
ROM       = Address:[FF00..1FFFF];
EXRAM     = Address:[20000..7FFFF];
IO        = Address:[20000..200FF];

!RAM_CS   = (RAM & !ROM) # (EXRAM & !IO);
!ROM_CS   = ROM & RW;
!IO_CS    = IO;

But the following logic does work:
Code:
FIELD Address = [A18..A8];
ROM       = Address:[FF00..1FFFF];
IO        = Address:[20000..200FF];

!ROM_CS   = ROM & RW;
!IO_CS    = IO;
!RAM_CS   = ROM_CS & IO_CS;

Are the two expressions for RAM_CS equivalent? The second one does work in my build so far, but my operating system is still a bit basic.

Edit (5/17/2022):
For those finding this in the future, the straightforward:
Code:
FIELD Address = [A18..A8];
RAM       = Address:[0000..FEFF];
ROM       = Address:[FF00..1FFFF];
IO        = Address:[20000..200FF];
EXRAM     = Address:[20100..7FFFF];

!RAM_CS   = RAM # EXRAM;
!ROM_CS   = ROM & RW;
!IO_CS    = IO;

will compile in WINCUPL with the Quine-McCluskey minimization option enabled. Read further in this post for background and details.


Last edited by tmr4 on Tue May 17, 2022 5:16 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 8:41 pm 
Offline
User avatar

Joined: Sat Jul 24, 2021 1:37 pm
Posts: 282
Hey! Fun problem to work through, hope this helps.

From the first logic we get, using DeMorgan's law:

RAM_CS = !(RAM & !ROM) & !(EXRAM & !IO)

We can apply the law to each group as well, which gives:

RAM_CS = (!RAM # ROM) & (!EXRAM # IO)

Or, because distributivity works in boolean logic,

RAM_CS = !RAM&!EXRAM # ROM&!EXRAM # !RAM&IO # ROM&IO

From your memory map

!RAM&!EXRAM is equal to a subset of ROM [10000..1FFFF]
ROM&!EXRAM is equal to ROM
!RAM&IO is equal to IO
ROM&IO is always false

so RAM_CS = [10000..1FFFF] # ROM # IO # 0 = ROM # IO (we can ignore the subset of ROM ORed with ROM)

From your second logic

!RAM_CS = ROM_CS & IO_CS gives you RAM_CS = !ROM_CS # !IO_CS = (ROM & RW) # IO

So they're not strictly equivalent. The second one also activates the RAM when you're writing to ROM space, which you might not want because your RAM doesn't occupy the whole ROM space.

_________________
BB816 Computer YouTube series


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 9:08 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8514
Location: Midwestern USA
tmr4 wrote:
I'm trying to code the following memory map for the PLD address decoder for my 65816:

What type of PLD is this you are programming? That matters if we are going to help you figure out your problem. Also, could you please post the entire source code, not just fragments?

You should be aware that placing I/O hardware at an extended address as you are doing will probably complicate your firmware in several ways. Assuming I/O is interrupt-driven, you would be forced to use long addressing for all I/O accesses within the interrupt handler (ISR). That will negatively affect performance because inter-bank accesses require one more clock cycle per access than do accesses using 16-bit (absolute) addressing. In particular, long accesses using R-M-W instructions will incur multiple clock cycle “penalties” per instruction.

As described here, succinctness is a primary goal in ISRs—time spent processing IRQs is time not available to process foreground tasks. The fewer addressing shenanigans required to access I/O and runtime data structures, the more performance you will achieve at any given Ø2 rate.

Alternatively, you could split your ISR between bank $00 and the I/O bank and JML to the rest of the ISR. The single long jump would be less “expensive” than repeated long accesses. However, since the ISR's preamble must be in bank $00, you may find firmware development to be too convoluted. Either way, I recommend you give this more thought before committing to a particular memory map.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 9:27 pm 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
akohlbecker wrote:
So they're not strictly equivalent. The second one also activates the RAM when you're writing to ROM space, which you might not want because your RAM doesn't occupy the whole ROM space.

Thanks Adrien. Very interesting analysis. I always struggle getting my head around those equivalencies.

I'm not sure I understand your last point. Physically my RAM does occupy the entire ROM space as I'm using 256k ROM and 512k RAM, both directly wired to the address bus. Thus, as you point out, a write to ROM would also write to RAM, but from a system perspective would be nonconsequential since nothing is expected in the overlapping region.

Better still, in my system, writes to ROM would be an error. I hope I don't have any of those.


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 10:03 pm 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
BigDumbDinosaur wrote:
What type of PLD is this you are programming?

I'm using an ATF22V10C.

BigDumbDinosaur wrote:
Also, could you please post the entire source code, not just fragments?

My complete code in the second case is:
Code:
Device   g22V10 ;

/* Input */
Pin 1        = CLK;
Pin 2        = RW;
Pin [3..10]  = [A15..A8];
Pin 11       = A16;
Pin 22       = A17;
Pin 13       = VIA_IRQ1;
Pin 14       = A18;
Pin 15       = ACIAs_IRQ;

/* Output */
Pin 16 = CLKB;
Pin 17 = WE;
Pin 18 = ROM_CS;
Pin 19 = RAM_CS;
Pin 20 = OE;
Pin 21 = IO_CS;
Pin 23 = IRQ;

/* Local Variables */
FIELD Address = [A18..A8];

/* Logic */
ROM       = Address:[FF00..1FFFF];
IO        = Address:[20000..200FF];

CLKB      = !CLK;
!WE       = CLK & !RW;
!OE       = CLK & RW;
!ROM_CS   = ROM & RW;
!IO_CS    = IO;
!RAM_CS   = ROM_CS & IO_CS;
IRQ       = VIA_IRQ1 & ACIAs_IRQ;

I'm using WINCUPL and the TL866II+ programmer.

BigDumbDinosaur wrote:
You should be aware that placing I/O hardware at an extended address as you are doing will probably complicate your firmware in several ways. Assuming I/O is interrupt-driven, you would be forced to use long addressing for all I/O accesses within the interrupt handler (ISR).

Some of my I/O is interrupt driven. My ISR is in ROM located in the page $FF00. I placed I/O in bank 2 because that's where I'm currently placing RW data. Thus, by setting the data bank register to 2 I can address both my data and I/O with 16-bit absolute address. I figured I'd use another address mode for banks above that, but I'll admit I haven't given it much thought. At this point even three banks of memory seems like a luxury. Honestly, I have on my to do list some of your posts on the subject, so I have a lot more learning to do.

BigDumbDinosaur wrote:
Either way, I recommend you give this more thought before committing to a particular memory map.

Yes, this is a work in progress. I'm not wedded to a particular scheme yet. So far I've just converted my hardware related code from the 6502. As I convert my Forth related code I'm sure I'll come across things that require more consideration.


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 10:30 pm 
Offline
User avatar

Joined: Sat Jul 24, 2021 1:37 pm
Posts: 282
tmr4 wrote:
akohlbecker wrote:
So they're not strictly equivalent. The second one also activates the RAM when you're writing to ROM space, which you might not want because your RAM doesn't occupy the whole ROM space.

Thanks Adrien. Very interesting analysis. I always struggle getting my head around those equivalencies.

I'm not sure I understand your last point. Physically my RAM does occupy the entire ROM space as I'm using 256k ROM and 512k RAM, both directly wired to the address bus. Thus, as you point out, a write to ROM would also write to RAM, but from a system perspective would be nonconsequential since nothing is expected in the overlapping region.

Better still, in my system, writes to ROM would be an error. I hope I don't have any of those.


I was looking at this specifically:
Code:
RAM       = Address:[0000..FFFF];
ROM       = Address:[FF00..1FFFF];


RAM is overlapping ROM between FF00 and FFFF. Which I didn't think through fully to realize you were overlaying them on top of each other in the chip select definitions below.

For clarity/safety you might want to write RAM = Address:[0000..FEFF]. Even though it works in this file, since you're doing RAM & !ROM for RAM_CS, you're one mistake away (forgetting to &!ROM) from enabling both chip selects at the same time. Same thing for EXRAM = Address:[20100..7FFFF]. Then you can also do !RAM_CS = RAM # EXRAM which is easier to read :wink:

_________________
BB816 Computer YouTube series


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 10:34 pm 
Offline
User avatar

Joined: Sat Jul 24, 2021 1:37 pm
Posts: 282
Also agree that putting I/O in bank 0 is the way to go; I use the end of the core RAM space for this in my design.

Attachment:
memory_map.png
memory_map.png [ 65.3 KiB | Viewed 49101 times ]


EDIT: Although, with changing the data bank register that might work. Sorry it is late I missed that part of your message.
So if data is in bank 2, what are you putting in bank 0 besides the direct page? Stack?

_________________
BB816 Computer YouTube series


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 10:46 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8514
Location: Midwestern USA
tmr4 wrote:
BigDumbDinosaur wrote:
What type of PLD is this you are programming?

I'm using an ATF22V10C.

All the more reason to be conservative with your memory map. The 22V10 doesn't have a lot of logic resources. The more granular your hardware decoding becomes, the greater the likelihood of coming up with a design that won't fit.

Quote:
My complete code in the second case is...I'm using WINCUPL and the TL866II+ programmer.

What is the purpose of the CLKB output?

Here are some observations, in no particular order:

  • Implementing Adrien's optimizations should help reduce your logic’s “footprint,” making it easier to fit the design to the 22V10. WinCUPL tries to “de Morganize” logic as much as possible. However, more succinct logic statements are preferable to relying on an internal optimization.

  • If possible, negative logic should not be used to drive outputs. If an output is considered true when low, declare the pin as inverted, e.g.:

    Code:
    Pin 20 = !OE;

    ...and:

    Code:
    OE       = CLK & RW;

    In other words, let the PLD's hardware do the output negation and write your logic as positive. This form will use fewer product terms than the way you’ve got it.

Quote:
Some of my I/O is interrupt driven.

Some? Why not all of it? The 816, like the 8-bit 6502 family, has excellent interrupt performance. Why not take advantage of that?

Quote:
My ISR is in ROM located in the page $FF00.

That only gives you 256 bytes in which to stuff the ISR, along with the hardware vectors, and the reset front end. I think you will regret that choice once you get serious about writing reasonably-comprehensive firmware.

Something else to consider is this: if an application is running in any bank other than the one in which the firmware is visible and if the firmware API is accessed as a set of subroutines, the application will be using JSL — RTL to call APIs. Those two are some of the slowest instructions in the 816's ISA. The alternative method for calling APIs, and the one I am using, is to use COP and an API index as COP’s signature, a method that is bank-agnostic and as fast as JSL — RTL.

Quote:
I placed I/O in bank 2 because that's where I'm currently placing RW data. Thus, by setting the data bank register to 2 I can address both my data and I/O with 16-bit absolute address.

Yes, that will work. However, if you decided to use the 816’s stack relative addressing mode, it will be accessing bank $00—the data bank register (DB) will not be a factor in generating the effective address.

Quote:
I figured I'd use another address mode for banks above that, but I'll admit I haven't given it much thought. At this point even three banks of memory seems like a luxury. Honestly, I have on my to do list some of your posts on the subject, so I have a lot more learning to do.

While use of a PLD in place of discrete logic does confer flexibility, as well as greatly reduce chip count, the 816 itself knows nothing about all that. You should arrange the memory map to minimize cross-bank accesses as much as possible. When cross-bank accesses become necessary, the [<dp>] and [<dp>],Y addressing modes are your friend.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 11:06 pm 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
akohlbecker wrote:
For clarity/safety you might want to write RAM = Address:[0000..FEFF].

The problem is this requires more product term than the PLD pin provides. That's why I've used the overlapping range with logic to exclude the ROM range from RAM.


Top
 Profile  
Reply with quote  
PostPosted: Sat May 14, 2022 11:10 pm 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
akohlbecker wrote:
So if data is in bank 2, what are you putting in bank 0 besides the direct page? Stack?

Direct page, return stack and Forth data stack and likely multiple instances of them. But this is just a thought at this point. Much like the 6502 zero page, it seems a waste to use the 65816 bank 0 for normal data and code.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 15, 2022 12:03 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
Hmm, several new posts. I'm falling behind! :) But...

akohlbecker wrote:
Also agree that putting I/O in bank 0 is the way to go
... can either of you clarify this preference for I/O being in Bank $00? I do see why I might choose Bank $00 for I/O because of hardware issues. For example, maybe I intend to use an 8-input NOR gate to decode the bank address, in which case banks other than $00 don't go with the flow -- $00 is very clearly preferred because it agrees with how I want the hardware arranged.

But I don't see much value in choosing Bank $00 for I/O because of software issues, even in the context of an ISR.

BigDumbDinosaur wrote:
you would be forced to use long addressing for all I/O accesses within the interrupt handler (ISR). That will negatively affect performance because inter-bank accesses require one more clock cycle per access than do accesses using 16-bit (absolute) addressing.

It's true that long accesses incur a penalty, and of course we'd prefer to avoid that. But I'm doubtful that having I/O appear in Bank $00 is of much use for avoiding that penalty.

The Program Bank Register PBR gets pushed to stack when an interrupt is recognized, and also a new value (ie, $00) written to it. This is crucial. The Data Bank Register DBR does not get pushed to stack (booboo edited as per your correction, tmr4 -- thx), and -- more to the point -- neither is a new value written to it -- DBR remains unchanged from the value it had prior to the interrupt.

So, if the ISR explicitly updates DBR then the penalty can be avoided. Or, the DBR update becomes unnecessary if we make a rule that the foreground code must disable interrupts anytime it wants to use a DBR value other than $00, but this is an unappealing compromise. That's why I say having I/O appear in Bank $00 isn't a very useful shortcut for avoiding the penalty

I'm drawing a blank on your suggestion to "split your ISR between bank $00 and the I/O bank and JML to the rest of the ISR." Like an interrupt, JML fails to update DBR. And can you supply a reference for your statement that long accesses using R-M-W instructions will incur multiple clock cycle penalties per instruction? Long address modes do mean the instruction will include a three-byte address, and certainly it costs an extra cycle to fetch that additional byte. But AFAIK the penalty only applies once, even in the case of a R-M-W

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Last edited by Dr Jefyll on Sun May 15, 2022 12:42 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sun May 15, 2022 12:10 am 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
Thanks for the review and comments. I still have a lot to consider. In answer to your questions:

BigDumbDinosaur wrote:
What is the purpose of the CLKB output?

It provides the inverted clock signal needed by the bank address latch and data bus buffer. Using the PLD for this saves a chip in my build. I've attached an image of the breadboard version (btw - I got the 64k, 12 ns RAM version of this up to 10 MHz before I started seeing stability issues). Ultimately I plan on a small handheld unit.

BigDumbDinosaur wrote:
  • Implementing Adrien's optimizations should help reduce your logic’s “footprint,” making it easier to fit the design to the 22V10. WinCUPL tries to “de Morganize” logic as much as possible. However, more succinct logic statements are preferable to relying on an internal optimization.

I've tried a number of ways to express the logic but the second method was the only one I got to work for a single page of ROM at the end of bank 0. I could get the wider ROM range of $F000-$FFFF to work with RAM of $0000-$EFFF, but I don't need that much ROM in bank 0. I'll see if some of Adrien's other suggestions work though. I'm definitely a novice on WINCUPL.

BigDumbDinosaur wrote:
  • If possible, negative logic should not be used to drive outputs. If an output is considered true when low, declare the pin as inverted, e.g.:

    Code:
    Pin 20 = !OE;

    ...and:

    Code:
    OE       = CLK & RW;

    In other words, let the PLD's hardware do the output negation and write your logic as positive. This form will use fewer product terms than the way you’ve got it.

Looks like I picked up some bad coding practices on the web. I guess it's easy to do. There are so few examples of concise coding for address decoders. Lots of bad stuff though, like where they do all of WINCUPL work on a spreadsheet and then import it.

BigDumbDinosaur wrote:
Quote:
Some of my I/O is interrupt driven.

Some? Why not all of it? The 816, like the 8-bit 6502 family, has excellent interrupt performance. Why not take advantage of that?

I'm using the 65C51 and the transmitter can't be interrupt driven.

BigDumbDinosaur wrote:
Quote:
My ISR is in ROM located in the page $FF00.

That only gives you 256 bytes in which to stuff the ISR, along with the hardware vectors, and the reset front end. I think you will regret that choice once you get serious about writing reasonably-comprehensive firmware.

My reset front end is in bank 1. I long jump to it from page $FF00. As I've seen you advise, my ISR is short.

BigDumbDinosaur wrote:
Something else to consider is this: if an application is running in any bank other than the one in which the firmware is visible and if the firmware API is accessed as a set of subroutines, the application will be using JSL — RTL to call APIs. Those two are some of the slowest instructions in the 816's ISA. The alternative method for calling APIs, and the one I am using, is to use COP and an API index as COP’s signature, a method that is bank-agnostic and as fast as JSL — RTL.

Thanks. I'll keep this in mind. Right now I'm very far from fully using much of either bank 1 or 2. My 6502 hardware and Forth primitives use less than 9k of ROM and my Forth code and dictionary probably use an equal amount of RAM. I expect that to go down with the 65816.

BigDumbDinosaur wrote:
Quote:
I placed I/O in bank 2 because that's where I'm currently placing RW data. Thus, by setting the data bank register to 2 I can address both my data and I/O with 16-bit absolute address.

Yes, that will work. However, if you decided to use the 816’s stack relative addressing mode, it will be accessing bank $00—the data bank register (DB) will not be a factor in generating the effective address.

Yes, I still have a lot to think about along those lines.

BigDumbDinosaur wrote:
Quote:
I figured I'd use another address mode for banks above that, but I'll admit I haven't given it much thought. At this point even three banks of memory seems like a luxury. Honestly, I have on my to do list some of your posts on the subject, so I have a lot more learning to do.

While use of a PLD in place of discrete logic does confer flexibility, as well as greatly reduce chip count, the 816 itself knows nothing about all that. You should arrange the memory map to minimize cross-bank accesses as much as possible. When cross-bank accesses become necessary, the [<dp>] and [<dp>],Y addressing modes are your friend.

I think I was hoping to rely on one of the busted myths form your 65816 Facts and Myths post, that the 65816 banks could be addressed as a whole. I haven't considered it much beyond that, so it's currently just an idea (hope?) that I'm hanging onto right now.


Attachments:
File comment: Build 4 w/ 65816 and 512k memory, running 6502 Forth in emulation mode
build4_exram_working.jpg
build4_exram_working.jpg [ 3.73 MiB | Viewed 49088 times ]
Top
 Profile  
Reply with quote  
PostPosted: Sun May 15, 2022 12:13 am 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
Dr Jefyll wrote:
Program Bank Register PBR and Data Bank Register DBR both get pushed to stack when an interrupt is recognized, but according to the doc I've seen so far only PBR gets a new value (ie, $00) written to it, whereas DBR remains unchanged.

I'm really new to the game here, but I thought only the program bank register was pushed to the stack on an interrupt.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 15, 2022 12:25 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
tmr4 wrote:
I'm really new to the game here, but I thought only the program bank register was pushed to the stack on an interrupt.
Doh -- sorry, you're right; DBR doesn't get pushed. My mistake; comes with typing in a hurry. However, I did and still do mean to say that, unlike PBR, DBR does not get a new value placed in it when an interrupt occurs. This is central to my post.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Sun May 15, 2022 12:33 am 
Offline

Joined: Sat Feb 19, 2022 10:14 pm
Posts: 147
Dr Jefyll wrote:
However, I did and still do mean to say that, unlike PBR, DBR does not get a new value placed in it when an interrupt occurs. This is central to my post.

Yes and that's what my current system relies on. DBR is set to bank 2 on start up and with I/O in bank 2 I don't need to change it again, either in the ISR or in my other code.

Still to be considered though is what I do when I exceed bank 2 for data. Still thinking about that.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 76 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 12 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: