6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 9:36 pm

All times are UTC




Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Oct 25, 2022 6:24 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
I started some time ago to modify the 65C02 core by David Banks and Ed Spittles, which is based on Arlet Ottens compact 6502 core. The main focus was a MMU that could help with memory access and protection. In the end I also added the WDC/Rockwell 65C02 instructions that were missing:

SMB $ZP,
RMB $ZP,
BBS $ZP,branch
BBR $ZP,branch
WAI
STP

These have been documented elsewere, so no need to say anything about them. This release is currently a beta release, so if you find bugs, push it into this tread so they can be fixed.

If you don't care about a MMU, you don't need to know anything else. You can use the core as a WDC65C02 or R65C02.

The MMU

The 65C02 is a fabulous CPU, but it lacks one essencial feature: A way to properly access memory! A MMU is not only a way to access more memory, but also a way to protect that memory. Think about Linux or any other system that needs memory protection. And that is essencially what you get with this MMU. Plus a way to efficiently access all that memory (without having to think about shifting memory banks around).

The MMU uses unused opcodes, so it doesn't really interfere with the 65C02. In the future I may also release the MMU as a separate unit so that one can use a real 65C02 and get the same features.

The following is a list of MMU instuctions with opcodes. They are explained in the attached file.
Code:
$82 $xy      MMS #$xy    Memory Management System register
$44 $ZP      MMB $ZP     Memory Management BLOCK register from Zero Page
$E2 $xx      MMB #$xx    Memory Management BLOCK register
$C2 $xx      MMZ #$xx    Memory Management Zeropage BANK register
$5C $xx $yy  MMI $yyxx   Memory Management Interrupt BANK register
$D4 $ZP      MMP $ZP,X   Memory Management Protection Table from Indexed Zeropage
$FC $xx $yy  MMF $yyxx   Memory Management Fetch BANK register
$54 $ZP      MMF $ZP,x   Memory Management Fetch BANK register from Indexed Zeropage 
$DC $xx $yy  MMJ $yyxx   Memory Management Jump BANK register
$F4 $ZP      MMJ $ZP,X   Memory Management Jump BANK register from Indexed Zeropage


Please note that this is a beta release so that slight adjustments may be needed to fix some (unknown) bug. The address bus is currently 20 bits.

-------------------------------------------------------------------------------------------

Update 20221028:
Updated core to correctly handle MMI instruction. MMJ_STORAGE flag now resides at bit 6 of the MMZ instruction (manual needs update).


Attachments:
65c02mmu-20221028.zip [17.15 KiB]
Downloaded 66 times
65C02MMU-manual-v1.pdf [264.89 KiB]
Downloaded 107 times


Last edited by kakemoms on Fri Oct 28, 2022 7:28 am, edited 1 time in total.
Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 25, 2022 8:55 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
Wow - quite a lot to digest there! Quite a lot of functionality, too, as far as I can tell.

If I tried to summarise, I'd say that this can map either a single 4k or a whole 64k from high memory into the 64k addressable space, and also can remap zero page (or page one? Or both?) into high memory, and can map an ISR into high memory... but I'm not completely sure! There seems also to be some possible separate treatment of code and data, or of reads and writes?


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 8:07 am 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1042
Location: near Heidelberg, Germany
I'm trying to understand, but I am missing an overview on what internal state the MMU has, how it is used in addressing,.

I find it difficult to deduce the effects on the addressing of normal opcode addresses just from the description of the opcodes that change this state

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 11:40 am 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Well, I guess the manual was rather short. But I will improve it over time, so hopefully it gets better.

Anyway I will try to explain:

- The MMU either act on a 4KB memory block (4K-mode) of your choice or on the full 64KB block (64K-mode). This can be changed by the MMS instruction by setting or clearing the flag "FULL_BLOCK_MOVE". If you change this several times in a program, be careful as the program counter (PC) is affected by the JBANK register (it changes the 4 MSB of the 20-bit address bus in the current implementation).

- The Zero page can be moved around with the ZBANK register. In 4K-mode, the ZBANK affects bits 20-12 and in 64K-mode the ZBANK affects bits 20-16. Every memory block (4KB blocks in 4K-mode or 64KB blocks in 64K-mode) can have a Zero page that is located in another memory block. If you don't touch the ZBANK register, all Zero page accesses will be into $0000-$00ff (as normal 65C02 operation). Note that stack location also changes with ZBANK, but always starts at Zero Page location+$100. Thus you have to be careful when moving Zero Page location around as it can affect the next RTS. A safe way is to set all Zero page locations before you start using the stacks.

- In 4KB mode, 60KB of the 0-64KB adressable range of the 65C02 is not affected by the MMU. E.g. it operates "normally" as without a MMU. The rest (4KB) that is affected by the MMU can be located in any 4KB block in the 0-64KB adressable range. The location of this 4KB block is decided by the BLOCK register. If for example BLOCK register=$A, the 4KB block that starts at $A000 is going to be the MMU memory range ($A000-$AFFF). Any code or access by the 65C02 into this memory range will be controlled by the MMU. The rest of the memory is not controlled by the MMU, but can be affected by it anyway.

As you may understand this gives some interesting possibilities. For example, if we go into 4K-mode, set the BLOCK register to $A and jump into the $A000-$AFFF memory range with MMJ set to $00 (=JBANK set to $00), the MMU will allocate memory area $000-$FFF as the $A000-$AFFF. It means that the Zero page of non-MMU memory will also start from $A000 (I know, kind of confusing). If you now set ZBANK register (which always sets ZBANK of the current JBANK to (for example) $1, the Zeropage of the current MMU block will be allocated to $1000 (even more confusing). The thing is that if code in the $A000-$AFFF area is now running and accessing Zero Page, the bytes will appear at $1000-$10FF (which by coincidence is also visible in non-MMU memory area).

I will try to make some drawings to better visualize this as it can be very confusing, but it all makes sense once you understand it. E.g. in 4K-mode, the whole memory is divided into 4KB blocks and whatever block you see will appear in the $A000-$AFFF area (in the above example) according to what the MMJ or MMF instructions sets as the block number. Each block can have its zero page located in another block, which gives versatility.

4K-mode address:
Code:
<----bits decided by MMU---><--bits decided by 65C02-->
[20 19 18 17 16 15 14 13 12][11 10 9 8 7 6 5 4 3 2 1 0]


The bits decided by MMU is set using MMJ (for JMP or JSR, e.g. PC location) or MMF for reading or storing (LDA/X/Y,STA/X/Y,INC,PHP,TSX and so on...). MMJ sets the JBANK register and MMF the FBANK register. In 4K-mode, bits 20-12 of the PC are equal to the current JBANK register.

For example program starts at $1000 in non-MMU mode:

Code:
$1000   MMS %10101010   Set MMU to standby
$1002   MMS %01010101   Start MMU
$1004   MMS %01000000   Set MMU flags: BANK_REG_MEM=OFF, IGNORE_LSB=OFF, IBANK MODE/MMJ STORE=OFF, SWAP=0, FULL_BLOCK_MOVE=OFF (4K-mode), PROTECTED_MEMORY=OFF
$1006   MMB #$f         Set BLOCK register to $f ($f000-$ffff is MMU memory)
$1008   MMF $0010      Set MMU memory block to start at $0010000


At this point, any access to $f000-$ffff by the 65C02 will either read or store bytes in the $0010000-$0010fff memory area (or $10000-$10fff if you trunkate it to 20 bits). So we can do that with ease:
Code:
$100B   LDA #$D0         Load the value $D0 into accumulator
$100D   STA $f001      Store the accumulator value into $10001


So in practice, the MMU does banking but in a more transparent way. If you copy some code to $10000, you can then jump to this code using JMP or JSR with a preceding MMJ:
Code:
$100B   MMJ $0010
$100E   JMP $f000      Jump to $10000


As you are probably thinking; how does this work with JSR and RTS? Well, there is a JBANK stack that stores current JBANK value so that any RTS will return to the previous JBANK. Just as the normal 65C02 stack, but stored in another location. The JBANK stack can be exposed by setting the BANK_REG_MEM to ON (it will then be exposed at $0100-$01ff until BANK_REG_MEM is set to OFF).

For 64K-mode, the whole adressable 65C02 range is banked (and shifted with the different BANK registers). Thus there is no non-MMU memory in 64K-mode, and the whole memory is divided into 64KB blocks (instead of 4KB blocks as above). Zero page location will always start at $0000 in the current memory block, but can be located at other memory blocks using the ZBANK register. E.g. several 64KB program blocks can share the same Zero page.


Last edited by kakemoms on Wed Oct 26, 2022 12:03 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 12:01 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1042
Location: near Heidelberg, Germany
Sorry I'm still confused. So, block selects a 4k window in the CPU address space, and MMf will select the physical address for normal load/store access, MMJ similarly for control jumps. Right?

I didn't get the part of the zeropage mapping.

Some drawings would be nice, esp how the CPu address is translated to physical address. Maybe a rewrite of your explanation with terms defined more clearly would help as well (like always specify either 'CPU address' or 'physical address' ie where it is in an attached memory chip.

So, if I get it right, you can have either a 4k window in the CPUs 64k CPU address space, that you can map to anyof 256 4k blocks in the 1M physical address space - or map the entire 64k CPU address space to any of 16 64k blocks in physical memory. I wonder what is the use case behind it? The first one allows for something like RAM disks. The second one ... what do you want to achieve?

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 12:48 pm 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Ok some ASCII drawing may help:

Lets divide 65C02 memory range into 4KB blocks and number them from 0 to F:

Code:
00 01 02 03 04 05 06 07 08 0A 0B 0C 0D 0E 0F

In this drawing, $02 is the $2000-$2FFF memory range and so on.. This is the normal 65C02 representation of memory. There is nothing above 0F here since $F000-$FFFF is the last 4KB block that the 65C02 can access.

Now we go into 4K-mode and set block E as MMU memory. We have $FF blocks of 4KB memory (total 1MByte), so the MMU can point to any of those. Lets start with pointing MMU memory to to block $10 using MMF. I will draw the memory block above and below the current MMU memory block on the line above or below to better visualize it:

Code:
MMF memory map
                                       0F
00 01 02 03 04 05 06 07 08 0A 0B 0C 0D 10 0F
                                       11

The MMF affects were data is read or written to, but not were the program is running (e.g. not the PC).

So, with the above memory map, if we read or write something from $E000, the 65C02 thinks it is accessing $E000, but the MMU redirects it to $10000 (since $10 has taken $0E's place). That happens with any instruction that reads or writes to memory, but not the actual program location (which is controlled by the PC).

The MMJ on the other part only affects were the program is running (the PC) and not the reading or writing address of the data. Now, lets use MMJ to try to get code running in $11000:

Code:
MMJ $11


The memory map then becomes

Code:
MMJ memory map
                                       10
00 01 02 03 04 05 06 07 08 0A 0B 0C 0D 11 0F
                                       12

so we can use:
Code:
MMJ $11
JMP $E000

From the 65C02 viewpoint we are jumping into the $0E memory block, but the MMU has replaced this with the $11 memory block when reading instructions, so the 65C02 is actually running code that starts at memory location $11000. Any reading or writing to memory by the instructions themselves will still be using the MMF memory map (e.g. to $10000-$10FFF), so code and memory access are not necessarily linked.

The Zero Page location of the 65C02 is always at $0000-$00FF with stack at $0100-$01FF. This resides at the firsts 4KB block of the 65C02 memory map.

The ZBANK register changes this so that any instructions that accesses Zero Page or Stack will be looking elsewere in the MMU memory. We represent this with replacing block $00 in the above memory map (even if the $0200-$0FFF range is not affected). Lets set it to $24 using MMZ:

Code:
MMZ/MMF memory map
23                                     0F
24 01 02 03 04 05 06 07 08 0A 0B 0C 0D 10 0F
25                                     11

Now, lets look at the instructions

Code:
LDA #$E0
STA $04
LDA #$00
STA $03
LDX #$01
LDA ($02,X)

Here, A(ccumulator) is loaded as $E0 and stored into $04 by the 65C02. The MMU changes the ZP address to $24004, so that is were $E0 is stored. Next, $00 is stored into $03 which becomes memory location $24003 as the MMU handles it.
Fourth line loads 1 into the X-register, then LDA reads a memory pointer from memory location ($02+1=)$03 and $04. The MMU also changes this to $24003 and $24004 as they are Zero page locations. The instruction then uses the content from $24003(=$00) and $24004(=$E0) as a pointer and reads from address $E000. Again the MMU recognizes $E000 as MMU memory and fetches data from $10000 according to the above memory map.


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 1:08 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
Overall I think this is very interesting but it does still need a clear explanation. Thanks for the two recent clarifications - they've helped me, at least.

So, it seems, in this 4k mode, you have three registers which affect memory mapping
MMF for data loads and stores, and rmw
MMJ for program fetches - opcodes and operand bytes
MMZ for page 0 and page 1 accesses

(It feels better to me to make this two very different discussions, one about the 4k mode and another about the 64k mode - in the manual you interleave these two mechanisms and I felt that made it more difficult to understand.)

It would be important, i think, to clarify that MMZ affects all accesses to pages 0 and 1, if that is true. That is, it does not selectively affect only zero page modes or stack accesses. If I
TSX
LDA 0103,X
then I would, I think, want to access the mapped page 1.


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 1:21 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
BTW, it may well be that different people have to take different routes to understanding this kind of thing.

For me, knowing that a big RAM chip has 20 address pins, and the 6502 has 16 address pins, I most need to know which bits have to go through a magic box, and what the configuration state of that box is. The actual names and numbers of the instructions which configure the box are a bit less important.

But in this case, if we perhaps have three different mechanisms all working for different purposes, we also need to know what the conditions are for each mechanism to kick in.

It feels like perhaps documentation is the hardest part!


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 26, 2022 6:39 pm 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
It's a nice idea, integrating it into the core. I know Andre has done this sort of thing externally before with great success. I also had a go a few years ago, seeing how far I could go without needing a formal MMU. The biggest challenge there was dealing with transitions - atomically paging the stack while returning to user code was I believe a sticking point. MMZ for example will mean you lose all thread context, so at that point you probably want to immediately execute an RTI or similar to pick up where whatever thread is using that zero page left off.

Edit - you'll also want to get the SP in the right place first...


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 27, 2022 7:10 am 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
Documentation is always the hardest part, but its a work-in-progress, so just bear with me.

There are indeed 3 registers that affect memory locations. ZBANK (Zero page and stack location), the JBANK stack (PC page location) and the FBANK (address read/write page location). Two registers IBANK and NBANK affects IRQ and NMI address vector locations, so you may want to add those.

The MMZ allows several things:

- When running simple banked memory, you can have more than one stack and more than one zeropage. More stack may be helpful in some programming environments (Fortran for example).

- When using more than 64KB of memory, you can have the Zero page point to the same place in all the memory blocks so that you don't loose that context when shifting to another 64KB block (or 4KB if you want to stick to 4K-mode for some reason).

- When using protected memory and shifting between supervisor mode and user mode, there are different Zero pages and stacks. It means that a subprocess in user mode can pass parameteres to the supervisor through the stack. This is important since the subprocess will never be able to read the memory area of the supervisor.

And that is were the MMU shines, its not just about banking memory. The signee of this posts is not going to give you Linux on the 6502, but with this MMU its at least theoretical possibility. Security in microcontrollers hasn't been on the agenda in the past, but recent developments will probably mean that one has to rethink that part.

I tried to put the MMU into a separate module, but the supervisor/user mode made it hard... at least I didn't succeed with adding it to a NMOS6502 based system...
Maybe a real 65C02 can get a MMU that retains all the features here. The RDY signal will certainly help, but there are a few obstacles to overcome.

Current bugs:
* The MMI instruction is either not there or has been lost somewere.

* There is a special mode were MMJ also affects the storage destination. The flag seems to be either misplaced or lost.

* Documentation version is more like V0.1 beta.

* Lack of programming examples


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 27, 2022 1:23 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1042
Location: near Heidelberg, Germany
Now that it has been mentioned... - here is my CPU board with an external MMU http://www.6502.org/users/andre/csa/cpu/index.html

It maps any of the 16 4k blocks in CPU address space to any of 256 4k blocks in the 1M physical address space.

Specifically it does:
- reset handling where the first write actually enables it
- use a No-Execute bit with the SYNC CPU output to signal an error condition on the bus
- use a No-Write bit with R/-W to signal an error condition on the bus when read-only pages are written to
- use Valid bit to signal an error condition on the bus if an invalid page is being accessed
- It is mapped into an IO area that is selected separately from the MMU translation so access does not get lost
- registers are accessible from the external bus when an external CPU controls it

I implemented a multitasking operating system for it that uses the mapping to separate memory for different processes.

The error conditions on the bus can trigger an external CPU to stop the main CPU and manipulate the MMU. I did plan for paging, but never implemented it. I did have the external CPU trace the main CPU with it using the no-exec condition.

Putting that into the CPU proper would be great, esp if error conditions could trigger an internal supervisor mode instead. You could also add a supervisor-only bit to protect memory from manipulation by user programs.

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 27, 2022 1:45 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
(Just to note, this uses a 74LS610 or equivalent, I think, which has a 16 word by 12 bit configuration. I suppose attaching it to an 8 bit bus reduces it to 16 words by 8 bits? Or perhaps with more circuitry the high nibbles could also be written.)


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 27, 2022 8:44 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1042
Location: near Heidelberg, Germany
Indeed. The first versions of the board only used 8 of the 12 bits for pure address mapping. Later I used a separate register (edit: that's the CPU control register IC6 in the schematics linked above) to store the no-exec, write protect and valid bits into further 3 MMU bits, i.e. now using 11 of the 12 bits.

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 28, 2022 5:06 am 
Offline

Joined: Wed Mar 02, 2016 12:00 pm
Posts: 343
gfoot wrote:
It's a nice idea, integrating it into the core. I know Andre has done this sort of thing externally before with great success. I also had a go a few years ago, seeing how far I could go without needing a formal MMU. The biggest challenge there was dealing with transitions - atomically paging the stack while returning to user code was I believe a sticking point. MMZ for example will mean you lose all thread context, so at that point you probably want to immediately execute an RTI or similar to pick up where whatever thread is using that zero page left off.

Edit - you'll also want to get the SP in the right place first...


Its what it was designed for. I will make programming examples which shows how to achieve this. The programmer must set up everything correctly for the secure memory layout to work, but all the tools are there.

MMZ can be used to make certain memory blocks stick to the same stack (so a memory area can span many blocks while sharing the same stack).

Going into user mode is always done using a JSR to a protected memory block. The return address is stored in the stack of the supervisor, then everything runs in the protected memory block until it encounters a RTS back to the supervisor. At that point, the return address is pulled from the supervisor stack and the cpu gets back there. There are no other ways to get out of the user mode memory, so efficiently a user mode process is isolated with no direct access to supervisor mode memory.

One must remember that MMJ (the J for Jump) will store the target memory block for the next JMP or JSR. That is stored on a separate stack (the JBANK stack). For that reason you always want to set up zero page/stack locations with MMZ before you start with the MMJ instructions. If you stick to that, you can prevent confusion. There is also only one JBANK stack, so that should be quite straight forward.

As for interrupt, the memory bank with the interrupt routine is specified through the IBANK register. Before any interrupt can occur, the programmer must specify the location of this bank with the MMI instruction. As an interrupt is triggered, the interrupt routine will pull the interrupt location from this bank, then execute it. Thus, the interrupt return address gets stored in the stack of the IBANK, and at an RTI it will correctly pull the right address, and the MMU will direct it towards the last JBANK (which was stored by the last MMJ).

Now there are probably ways to break this, but if the programmer adheres to a certain way of doing things, it should work.


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 28, 2022 7:59 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
Just to be clear, the JBANK stack is not a stack of values within the MMU, I think(*), rather the JBANK value points to a page-one mapping which replaces the 6502 stack. (Or indeed, both a page zero and a page one, because that's what you've chosen to build.)

(*) Edit: I was wrong, see downthread.


Last edited by BigEd on Fri Oct 28, 2022 9:15 pm, edited 1 time in total.

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

All times are UTC


Who is online

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