123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- --+-----------------------------------+-------------------------------------+--
- --| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
- --| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
- --| / __|/ _ \ / __|_ / _ \| | | | | |--
- --| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
- --| |___/\___/ \___/___\___/ \___/ | |--
- --| | http://sowerbutts.com/ |--
- --+-----------------------------------+-------------------------------------+--
- --| 4K paged Memory Management Unit: Translates 16-bit virtual addresses |--
- --| from the CPU into 26-bit physical addresses to allow more memory to be |--
- --| addressed. Also has a hack to allow unmapped physical memory to be |--
- --| accessed through an IO port which synthesises memory operations. |--
- --+-------------------------------------------------------------------------+--
- --
- -- The MMU takes a 16-bit virtual address from the CPU and divides it into a
- -- 4-bit frame number and a 12-bit offset. The frame number is used as an index
- -- into an array of sixteen registers which contain the hardware page numbers
- -- (the translation table). The physical address is then formed from the
- -- hardware page number concatenated with the 12-bit offset. My hardware page
- -- numbers are 14-bits long because I wanted a 64MB physical address space but
- -- you could use any length you wished.
- --
- -- So if the virtual address 0xABCD is accessed, we'd divide that into frame
- -- number 0xA (decimal 10), offset 0xBCD. If the 10th MMU translation register
- -- contains 0x1234 then the translated physical address would be 0x1234BCD.
- --
- -- My MMU is programmed using 8 I/O ports in the range 0xF8 through 0xFF. The
- -- chip select line is asserted for any access in that range and the MMU
- -- decodes the low three bits to determine which register is being accessed.
- --
- -- The port at 0xF8 is effectively a mux which selects the function of ports
- -- 0xFB through 0xFF.
- --
- -- Writing 0x00 through 0x0F to the function register at 0xF8 allows you to
- -- read/write one of the 16 MMU translation registers. With these selected;
- -- - 0xFC contains the high byte of the physical address
- -- - 0xFD contains the low byte of the physical address
- -- - 0xFB contains permission bits (read/write/execute, currently ignored)
- --
- -- So updating a mapping generally requires just three I/O writes: One to 0xF8
- -- to select which frame to modify, and one each to 0xFC and 0xFD to write out
- -- the new translation. The permission bits are programmable but currently
- -- ignored (I had planned to add some level of memory protection to UZI one day)
- --
- -- The "17th page" is a bit of a hack bolted on. The lazy programmer in me finds
- -- it much more convenient to sometimes access memory without remapping a frame;
- -- in particular you don't have to select which frame to remap such as to avoid
- -- remapping the memory pointed to by PC, SP or your source/target pointer.
- --
- -- Writing 0xFF to port 0xF8 selects the "17th page pointer". This is a 26-bit
- -- register but again could be wider/narrower as required. With this selected
- -- ports 0xFC, 0xFD, 0xFE, 0xFF are the register contents (with the high byte in
- -- 0xFC, low byte in 0xFF).
- --
- -- When the CPU reads or writes the I/O port 0xFA the MMU translates the I/O
- -- operation into a memory operation. The physical memory address accessed is
- -- the address contained in the 17th page pointer register. After the memory
- -- operation completes the 17th page pointer register is incremented so that
- -- repeated accesses to 0xFA walk forward through memory.
- --
- --
- -- MMU registers:
- --
- -- base = 0xF8 in standard socz80 system
- --
- -- base+0 mux frame select (write 00..0F to select frame, FF to select "17th page" pointer)
- -- base+1 (unused)
- -- base+2 17th page (read/write will address pointed memory and post-increment the pointer)
- -- base+3 page permissions
- -- base+4 page address (high byte) / ptr address (high byte)
- -- base+5 page address (low byte) / ptr address
- -- base+6 ptr address
- -- base+7 ptr address (low byte)
- --
- -- Basic operation of the MMU (older documentation, concise but still correct)
- -- For memory requests:
- -- The top 4 bits of CPU logical address are replaced with 14 bits taken
- -- from the MMU translation table entry indexed by those top four bits.
- -- For IO requests:
- -- Reads or writes to I/O port at (base+2) are converted into memory
- -- requests to the address pointed to by "the 17th page", a pointer which
- -- can be accessed by writing 0xFF to the frame select register (base+0)
- -- and then programming a 26-bit address into I/O ports FC FD FE FF. Each
- -- I/O request to port F/A results in the pointer being post-incremented
- -- by 1, which means that INIR our OUTIR instructions can read/write blocks
- -- of memory outside of the CPU logical address space through this port.
- --
- -- Note that the MMU has to insert a forced CPU wait state to give the
- -- addressed memory device time to read the new address off the bus.
- --
- library IEEE;
- use IEEE.std_logic_1164.all;
- use IEEE.numeric_std.all;
- entity MMU is
- port(
- clk : in std_logic;
- reset : in std_logic;
- address_in : in std_logic_vector(15 downto 0);
- address_out : out std_logic_vector(25 downto 0);
- cpu_data_in : in std_logic_vector(7 downto 0);
- cpu_data_out : out std_logic_vector(7 downto 0);
- cpu_wait : out std_logic;
- req_mem_in : in std_logic;
- req_mem_out : out std_logic;
- req_io_in : in std_logic;
- req_io_out : out std_logic;
- io_cs : in std_logic;
- req_read : in std_logic;
- req_write : in std_logic;
- access_violated : out std_logic
- );
- end MMU;
- architecture behaviour of MMU is
- -- each MMU entry looks like this
- type mmu_entry_type is
- record
- frame: std_logic_vector(13 downto 0);
- can_read: std_logic;
- can_write: std_logic;
- end record;
- -- the whole MMU state looks like this
- type mmu_entry_array is array(natural range <>) of mmu_entry_type;
- -- and here's our instance
- signal mmu_entry : mmu_entry_array(0 to 15);
- -- 17th page pointer (frame FF)
- signal mmu_frame_ff_pointer : std_logic_vector(25 downto 0);
- -- IO interface
- signal cpu_entry_select : std_logic_vector(7 downto 0);
- -- break up the incoming virtual address
- alias frame_number : std_logic_vector( 3 downto 0) is address_in(15 downto 12);
- alias page_offset : std_logic_vector(11 downto 0) is address_in(11 downto 0);
- signal map_io_to_ff_ptr : std_logic;
- signal was_map_io_to_ff_ptr : std_logic := '0';
- begin
- map_io_to_mem_proc: process(address_in, req_mem_in, req_io_in)
- begin
- if req_mem_in = '0' and req_io_in = '1' and address_in(7 downto 0) = "11111010" then
- map_io_to_ff_ptr <= '1';
- else
- map_io_to_ff_ptr <= '0';
- end if;
- end process;
- with map_io_to_ff_ptr select
- address_out <=
- mmu_entry(to_integer(unsigned(frame_number))).frame & page_offset when '0',
- mmu_frame_ff_pointer when '1';
- with map_io_to_ff_ptr select
- req_mem_out <=
- req_mem_in when '0',
- '1' when '1';
- with map_io_to_ff_ptr select
- req_io_out <=
- req_io_in when '0',
- '0' when '1';
- with map_io_to_ff_ptr select
- access_violated <=
- req_mem_in and ((req_read and not mmu_entry(to_integer(unsigned(frame_number))).can_read) or
- (req_write and not mmu_entry(to_integer(unsigned(frame_number))).can_write)) when '0',
- '0' when '1';
- -- force CPU to wait one cycle when we map IO to memory access; this
- -- is in order to give our synchronous memories a cycle to read the
- -- address, look up the data in synchronous memory, and provide a result.
- cpu_wait <= map_io_to_ff_ptr and (not was_map_io_to_ff_ptr);
- data_out: process(address_in, cpu_entry_select, mmu_entry, mmu_frame_ff_pointer)
- begin
- if cpu_entry_select = "11111111" then
- -- pointer (FF)
- case address_in(2 downto 0) is
- when "000" =>
- cpu_data_out <= cpu_entry_select;
- when "011" =>
- cpu_data_out <= "00000011";
- when "100" =>
- cpu_data_out <= "000000" & mmu_frame_ff_pointer(25 downto 24);
- when "101" =>
- cpu_data_out <= mmu_frame_ff_pointer(23 downto 16);
- when "110" =>
- cpu_data_out <= mmu_frame_ff_pointer(15 downto 8);
- when "111" =>
- cpu_data_out <= mmu_frame_ff_pointer(7 downto 0);
- when others =>
- cpu_data_out <= "00000000";
- end case;
- else
- -- 16 frames (00 .. 0F)
- case address_in(2 downto 0) is
- when "000" =>
- cpu_data_out <= cpu_entry_select;
- when "011" =>
- cpu_data_out <= "000000" & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_write & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_read;
- when "100" =>
- cpu_data_out <= "00" & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8);
- when "101" =>
- cpu_data_out <= mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
- when others =>
- cpu_data_out <= "00000000";
- end case;
- end if;
- end process;
- mmu_registers: process(clk)
- begin
- if rising_edge(clk) then
- if reset = '1' then
- mmu_entry( 0).frame <= "10000000000000"; -- first page of SRAM (monitor ROM)
- mmu_entry( 1).frame <= "00000000000001"; -- DRAM page 1
- mmu_entry( 2).frame <= "00000000000010"; -- DRAM page 2
- mmu_entry( 3).frame <= "00000000000011"; -- DRAM page 3
- mmu_entry( 4).frame <= "00000000000100"; -- DRAM page 4
- mmu_entry( 5).frame <= "00000000000101"; -- DRAM page 5
- mmu_entry( 6).frame <= "00000000000110"; -- DRAM page 6
- mmu_entry( 7).frame <= "00000000000111"; -- DRAM page 7
- mmu_entry( 8).frame <= "00000000001000"; -- DRAM page 8
- mmu_entry( 9).frame <= "00000000001001"; -- DRAM page 9
- mmu_entry(10).frame <= "00000000001010"; -- DRAM page 10
- mmu_entry(11).frame <= "00000000001011"; -- DRAM page 11
- mmu_entry(12).frame <= "00000000001100"; -- DRAM page 12
- mmu_entry(13).frame <= "00000000001101"; -- DRAM page 13
- mmu_entry(14).frame <= "00000000001110"; -- DRAM page 14
- mmu_entry(15).frame <= "10000000000001"; -- second page of SRAM
- mmu_entry( 0).can_read <= '1';
- mmu_entry( 1).can_read <= '1';
- mmu_entry( 2).can_read <= '1';
- mmu_entry( 3).can_read <= '1';
- mmu_entry( 4).can_read <= '1';
- mmu_entry( 5).can_read <= '1';
- mmu_entry( 6).can_read <= '1';
- mmu_entry( 7).can_read <= '1';
- mmu_entry( 8).can_read <= '1';
- mmu_entry( 9).can_read <= '1';
- mmu_entry(10).can_read <= '1';
- mmu_entry(11).can_read <= '1';
- mmu_entry(12).can_read <= '1';
- mmu_entry(13).can_read <= '1';
- mmu_entry(14).can_read <= '1';
- mmu_entry(15).can_read <= '1';
- mmu_entry( 0).can_write <= '0';
- mmu_entry( 1).can_write <= '1';
- mmu_entry( 2).can_write <= '1';
- mmu_entry( 3).can_write <= '1';
- mmu_entry( 4).can_write <= '1';
- mmu_entry( 5).can_write <= '1';
- mmu_entry( 6).can_write <= '1';
- mmu_entry( 7).can_write <= '1';
- mmu_entry( 8).can_write <= '1';
- mmu_entry( 9).can_write <= '1';
- mmu_entry(10).can_write <= '1';
- mmu_entry(11).can_write <= '1';
- mmu_entry(12).can_write <= '1';
- mmu_entry(13).can_write <= '1';
- mmu_entry(14).can_write <= '1';
- mmu_entry(15).can_write <= '1';
- mmu_frame_ff_pointer <= "10000000000000000000000000"; -- map first byte of ROM to pointer on reset
- was_map_io_to_ff_ptr <= '0';
- else
- was_map_io_to_ff_ptr <= map_io_to_ff_ptr;
- if io_cs = '1' and req_write = '1' then
- case address_in(2 downto 0) is
- when "000" =>
- cpu_entry_select <= cpu_data_in;
- when "011" =>
- if cpu_entry_select(7 downto 4) = "0000" then
- mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_read <= cpu_data_in(0);
- mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_write <= cpu_data_in(1);
- end if;
- when "100" =>
- if cpu_entry_select(7 downto 4) = "0000" then
- mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
- cpu_data_in(5 downto 0) & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
- elsif cpu_entry_select = "11111111" then
- mmu_frame_ff_pointer <=
- cpu_data_in(1 downto 0) & mmu_frame_ff_pointer(23 downto 0);
- end if;
- when "101" =>
- if cpu_entry_select(7 downto 4) = "0000" then
- mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
- mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8) & cpu_data_in(7 downto 0);
- elsif cpu_entry_select = "11111111" then
- mmu_frame_ff_pointer <=
- mmu_frame_ff_pointer(25 downto 24) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(15 downto 0);
- end if;
- when "110" =>
- if cpu_entry_select = "11111111" then
- mmu_frame_ff_pointer <=
- mmu_frame_ff_pointer(25 downto 16) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(7 downto 0);
- end if;
- when "111" =>
- if cpu_entry_select = "11111111" then
- mmu_frame_ff_pointer <=
- mmu_frame_ff_pointer(25 downto 8) & cpu_data_in(7 downto 0);
- end if;
- when others =>
- -- nothing
- end case;
- elsif map_io_to_ff_ptr = '0' and was_map_io_to_ff_ptr = '1' then
- -- post-increment our pointer (this is what makes "the 17th page" efficient!)
- mmu_frame_ff_pointer <= std_logic_vector(unsigned(mmu_frame_ff_pointer) + 1);
- end if;
- end if;
- end if;
- end process;
- end;
|