6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Sep 22, 2024 4:41 am

All times are UTC




Post new topic Reply to topic  [ 24 posts ]  Go to page Previous  1, 2
Author Message
 Post subject: Re: 6502 MMU
PostPosted: Mon Oct 14, 2019 9:46 am 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Sorry, didn’t see the questions until now. I had to spend some time at the hospital, so projects got halted for obvious reasons.

I have written a few code examples, but nothing very extensive. There are probably still a few bugs. The first code put things in memory with MMF $0011 and STA $A000,x (targeting $11000) and looping for X=0 to 255. It the copies that from $11000,X onto the screen, then it increases then value in $11000,X and loops (MMF+INC). Result is 256 characters on screen that loops through all values. It is stable (you immediately see glitches as all characters should be the same).

Second example did the same, but first copied the code into $10000 and used JSR to that code (MMJ $0010 and JSR $A000). Also works. I will post the code here once I have time.

Known bugs are still on the 4KiB page crossing, e.g. At the end of the MMU area. Quickfix is a JMP and no Bxx opcodes out of the $A000-$AFFF area. I hope to fix that soon.

Page size is up to the programmer. A 4KiB page seemed like a natural limit, but the code can handle any page size by changing a few variables.


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Mon Oct 14, 2019 7:34 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
BigEd wrote:
Thanks kakemoms. Is it always the A block or could one use any 4k block?


You can use any block. It's only a small change in the Verilog code. I could also add a register to control which 4KiB block to use, but it would require the memory subsystem to have an memory map that would allow it... (obviously)


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Sun Jul 19, 2020 8:29 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Just wanted to say that I am currently implementing a MMU into the Arlet's extended 65C02 core (by David and Ed).

I have all opcodes and banking working, but memory protection implementation is not finished. The opcodes are slighly different than the ones published here, much improved with regard to how a programmer needs them to be. They are still all based on "NOP"s in the 6502 and 65C02 that fetches extra bytes. Thus, no new opcodes. The idea is that one could write a separate MMU into a CPLD and plug in a real 6502 (or 65C02) to get the same functionality.

I will release it later (this year?) in the programmable logic section.


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Mon Jul 20, 2020 7:34 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Sounds great!


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Sun Jan 03, 2021 3:13 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
The last months went by in a flash. The 6502MMU is closer to being in a beta version, but a few features remains.

One of the features of this MMU is to enable multitasking and multibank use. I therefore wanted to do this in a way that makes it possible to run several tasks on the 65C02. Using memory banking has enabled us to switch between different stacks. Currently up to 256 different stacks and zero pages (and probably up to 65536 in the future). What is not so obvious it how to implement it so that nothing breaks. I need interrrupts to work and each task within its own bank (or several banks).

After some thinking I believe I have cooked it down to two ways to actually make it work. Both requires a "Supervisor level" and a "User level". The MMU can set a memory bank to being in Supervisor mode and thus not accessible from User level. It also sets a key to each bank that is in user level so that you can't jump into a bank from a different bank, unless you are in Supervisor mode. The idea is that the task manager (system) runs in Supervisor level, while the actual tasks runs in User level. Interrupts happen and the interrupt handler also runs in supervisor mode, but this is were things get complicated.
Once the task handler jumps into a task (and to User level) this happens with a JSR instruction into a memory bank that has its protection key set. Once in this bank, the 65C02 can't exit that bank using JMP or JSR (the MMU won't change the bank). There is only two ways to exit the bank (with the User level task) which is through an interrupt or through an RTS. The RTS return address in not accessible in the User level since it resides in a Supervisor level stack, so the "system" is protected from whatever happens inside a task. The task simply has no way to access the memory that the task manager and other system processes runs in.
I can solve the interrupt handling by using one of the following two methods:
1) The interrupt handler is invoked after a certain time. The reason for this is that the task is allowed a certain amount of time before it needs to stop so that the task handler can run the next task (in the task handler queue). The interrupt code runs in Supervisor level and needs to return to the task it interrupted at some point. This can be done immediately if there is code (in the task) that exits the task on the request from the interrupt handler. From a programming perspective, this makes the interrupt handler easier to do since it always returns to the last interrupt request via a RTI, and two interrupts won't happen nested inside each other. It is also the most demanding with respect to making tasks, so my guess it that its not a good idea.
2) The interrupt handler is invoked after a certain time as for (1), but the interrupt handler doesn't return to the current task at hand but invokes the next task in the task manager queue. At some point we need to return to the interrupted task, but the idea is that this happens in the next time slot (that we have for that task) and not before. In order for that to work we need to do two things:
i) Store the return address, stack pointer and processor status.
ii) Store the bank number in which the interrupt happened.

Let me show this in a semigraphical-way to simplify:
Method 1)
Code:
Supervisor level:
                JSR task1    -->  JSR task 2         --> Interrupt handler -> (code) -> RTI
                    |       /        |              /                                    V
                    |      |         |             |                                     |
                    |      |         |             |                                     |
User level:         V      |         V             |                                     |
                  Task1    |       Task2           |                                     |
                   CLI     |        CLI           /                                      |
                  (some)   |       (Interrupt) >--                                      /
                  (code)   |       (code) <---------------------------------------------
                   SEI    /         SEI
                   RTS----          RTS-----> (back to supervisor task handler)


Method 2)
Code:
Supervisor level:
                JSR task1    -->  JSR task 2         --> Interrupt handler -> (code) -> JSR task3
                    |       /        |              /                                       |
                    |      |         |             |                                        |
                    |      |         |             |                                        |
User level:         V      |         V             |                                        V
                  Task1    |       Task2           |                                      Task3
                   CLI     |        CLI           /                                        CLI
                  (some)   |       (Interrupt) >--                                        (some)
                  (code)   |        (code)                                                (code)
                   SEI    /         SEI                                                    SEI
                   RTS----          RTS  -----> (task manager)                             RTS  -----> (task manager)


and so on... As you may understand, the second method has an interrupt handler that is more complicated and interweaved with the task handler. Storing the bank number is also not so easy to do since the MMU doesn't have a way to report back on the active bank (at least at the moment). Still we know which bank we jumped into (from the task manager) and as long as we don't spread a task over several banks, we don't need to read the bank number.

There are probably many consequences that I can't see at this point, and there may be other ways to do it (which I haven't seen). Passing parameters between the task and the system is one bottleneck which requires some thinking, but all-in-all I haven't seen anything that prevents this method from working. A task that doesn't use CLI and therefore prevents an IRQ, can be handled with an NMI (timer driven) to prevent it from halting the system.

If anyone has some input to improved ways (or questions on why it is implemented this way), I am happy to hear you.


Last edited by kakemoms on Sun Jan 03, 2021 5:47 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Sun Jan 03, 2021 5:22 pm 
Offline
User avatar

Joined: Sun Oct 18, 2015 11:02 pm
Posts: 428
Location: Toronto, ON
This is a great idea. I'll be following with interest.

I did something similar as a quick experiment with the C74-6502 but did not fully develop the concept (see Logisim video here).

It uses the C74-6502's 24-bit addressing rather than an MMU, but the approach may nevertheless be helpful:
  • Task switching is accomplished strictly through NMI which is reserved.
  • Tasks run in separate banks until a regularly occurring NMI is triggered.
  • The supervisor runs as the NMI ISR and switches to the next task in the roster (which can be any task, including the current one, based on whatever dispatch-logic one wishes to implement),
  • The supervisor restores state for the selected task and jumps to it by executing an RTI. The task simply sees an RTI from the NMI that interrupted it earlier.
  • To launch a new task, the supervisor installs the start address as an RTI return address on the stack of the new task, and places it into the roster for execution.
  • Bank 0 is reserved for the supervisor to keep system data, including a task-roster with any special dispatch settings.
  • A "system call" can be used to allows tasks to request special dispatch handling by the supervisor. For example, a task can request that the supervisor continue to re-launch it until it completes some important task.

Hope some of that is helpful.

_________________
C74-6502 Website: https://c74project.com


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Mon Jan 04, 2021 6:28 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Thank you for the reply! First I have to say I am impressed by that massive CPU board of yours. Looks insane! :D

Drass wrote:
[*]Tasks run in separate banks until a regularly occurring NMI is triggered.

Yes, that sounds like a good way to do it. Using the IRQ means that one has to have the NMI as a "backup" to stop processes/tasks that crashes. Which works unless you have a "KIL" opcode on the 6502. Fortunately the 65C02 doesn't crash that way. Eventually it will be up to the programmer to decide which interrupt to use.
Drass wrote:
[*]The supervisor runs as the NMI ISR and switches to the next task in the roster (which can be any task, including the current one, based on whatever dispatch-logic one wishes to implement),
[*]The supervisor restores state for the selected task and jumps to it by executing an RTI. The task simply sees an RTI from the NMI that interrupted it earlier.

Makes sense to do that! I also think this is the best way to "make it work".
Drass wrote:
[*]To launch a new task, the supervisor installs the start address as an RTI return address on the stack of the new task, and places it into the roster for execution.

My implementation to use JSR is quite different to this. The reason is that a User_mode is triggered by jumping (using JSR) to a memory bank that has the protection_key set to something other than NUL. It also makes it possible to end a task using RTS. I am not sure I could get it to work without using JSR/RTS. Interrupt/RTI has to work almost the same way, but there are a few things that depend on the JSR. I will have to think about that.
Drass wrote:
[*]Bank 0 is reserved for the supervisor to keep system data, including a task-roster with any special dispatch settings.

Its the same here, but the supervisor can reside in any bank or combination of banks. It just needs to have the protection_key cleared for that bank.
Drass wrote:
[*]A "system call" can be used to allows tasks to request special dispatch handling by the supervisor. For example, a task can request that the supervisor continue to re-launch it until it completes some important task.

It is certainly important that the Supervisor can enable certain system resources for the tasks. I don't think I can have a special system call for that since it would break the memory/system protection. The plan is to either give the Supervisor parameters through the task stack or through a certain memory area (in the task bank). Again this will be up to the programmer on how to implement it, but it will mean that the task has to access I/O and other resources indirectly. It may not be the best way, so I have to think more about that! Maybe special system tasks should have access to certain resources. Using 4KiB banks could enable the Supervisor to control system task access if these are in separate 4KiB banks (you only need to change the memory_protection key before jumping into the task in question)... I will think about it.

Drass wrote:
Hope some of that is helpful.

Certainly is! Thanks!


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Tue Jan 05, 2021 3:04 am 
Offline
User avatar

Joined: Sun Oct 18, 2015 11:02 pm
Posts: 428
Location: Toronto, ON
kakemoms wrote:
I am impressed by that massive CPU board of yours. Looks insane! :D
Thanks for the mention! Cheers.:)

Quote:
My implementation to use JSR is quite different to this. The reason is that a User_mode is triggered by jumping (using JSR) to a memory bank that has the protection_key set to something other than NUL. It also makes it possible to end a task using RTS. I am not sure I could get it to work without using JSR/RTS. Interrupt/RTI has to work almost the same way, but there are a few things that depend on the JSR. I will have to think about that.
Perhaps a way to think about this is to look at the OS as being composed of two parts — a task switching dispatcher that runs in the background as an ISR, and a foreground user “shell” that runs as a task and allows the user to launch other tasks. When you ask the shell to “run” a task, it loads the file into memory and does a JSR to the task. Then the dispatcher periodically gives the CPU to the task to run. When the task completes, the task does an RTS back to the shell. In this sense, the Shell is just the “default” task and you can even have multiple shells for multiple users running concurrently.

Does that work?

Quote:
The plan is to either give the Supervisor parameters through the task stack or through a certain memory area (in the task bank).
I like your idea of a task-specific data area. But the dispatcher is an asynchronous process so you have to be careful. The normal thing would be for the foreground task to disable interrupts and pause the dispatcher while it writes to the message area. But that’s not possible on an NMI. An alternative is to have a special “message ready” location which the task sets with a single write. The dispatcher can then check for a “message ready” just before re-launching a task, execute whatever is there, clear the “message ready” flag, and RTI to the task. Very usefully, it is possible for a task to spawn another using this mechanism. A task can load some code into a bank, place an RTI return address in the stack in this bank and then message the dispatcher to add this new task to the dispatch roster. In due course, the dispatcher will see this message and indeed add the new task to the roster. At some later time, the dispatcher will get to the new task’s entry in the roster and RTI to it as normal. On system boot up you spawn an “original” task which has no parent, so it never does an RTS, but can itself also spawn other tasks. :)

_________________
C74-6502 Website: https://c74project.com


Top
 Profile  
Reply with quote  
 Post subject: Re: 6502 MMU
PostPosted: Sat Jan 16, 2021 9:57 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Drass wrote:
Perhaps a way to think about this is to look at the OS as being composed of two parts — a task switching dispatcher that runs in the background as an ISR, and a foreground user “shell” that runs as a task and allows the user to launch other tasks. When you ask the shell to “run” a task, it loads the file into memory and does a JSR to the task. Then the dispatcher periodically gives the CPU to the task to run. When the task completes, the task does an RTS back to the shell. In this sense, the Shell is just the “default” task and you can even have multiple shells for multiple users running concurrently.

Does that work?


I don't think so since the 6502mmu memory protection is based on only Supervisor_mode being able to launch other tasks. The User_mode task needs to ask the Supervisor_mode task to launch another task, but it cant access the other tasks directly.

Drass wrote:
I like your idea of a task-specific data area. But the dispatcher is an asynchronous process so you have to be careful. The normal thing would be for the foreground task to disable interrupts and pause the dispatcher while it writes to the message area. But that’s not possible on an NMI. An alternative is to have a special “message ready” location which the task sets with a single write. The dispatcher can then check for a “message ready” just before re-launching a task, execute whatever is there, clear the “message ready” flag, and RTI to the task. Very usefully, it is possible for a task to spawn another using this mechanism. A task can load some code into a bank, place an RTI return address in the stack in this bank and then message the dispatcher to add this new task to the dispatch roster. In due course, the dispatcher will see this message and indeed add the new task to the roster. At some later time, the dispatcher will get to the new task’s entry in the roster and RTI to it as normal. On system boot up you spawn an “original” task which has no parent, so it never does an RTS, but can itself also spawn other tasks. :)


Its the way I imagined it. Each task has its own Zeropage and Stack areas, so I was thinking of using that to contain special memory locations that trigger processes in the Supervisor_mode task (or dispatcher as you call it). That way you can get a message to the system, but you can't hack into it directly.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 24 posts ]  Go to page Previous  1, 2

All times are UTC


Who is online

Users browsing this forum: No registered users and 6 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:  
cron