MMU.vhd 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. --+-----------------------------------+-------------------------------------+--
  2. --| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
  3. --| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
  4. --| / __|/ _ \ / __|_ / _ \| | | | | |--
  5. --| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
  6. --| |___/\___/ \___/___\___/ \___/ | |--
  7. --| | http://sowerbutts.com/ |--
  8. --+-----------------------------------+-------------------------------------+--
  9. --| 4K paged Memory Management Unit: Translates 16-bit virtual addresses |--
  10. --| from the CPU into 26-bit physical addresses to allow more memory to be |--
  11. --| addressed. Also has a hack to allow unmapped physical memory to be |--
  12. --| accessed through an IO port which synthesises memory operations. |--
  13. --+-------------------------------------------------------------------------+--
  14. --
  15. -- The MMU takes a 16-bit virtual address from the CPU and divides it into a
  16. -- 4-bit frame number and a 12-bit offset. The frame number is used as an index
  17. -- into an array of sixteen registers which contain the hardware page numbers
  18. -- (the translation table). The physical address is then formed from the
  19. -- hardware page number concatenated with the 12-bit offset. My hardware page
  20. -- numbers are 14-bits long because I wanted a 64MB physical address space but
  21. -- you could use any length you wished.
  22. --
  23. -- So if the virtual address 0xABCD is accessed, we'd divide that into frame
  24. -- number 0xA (decimal 10), offset 0xBCD. If the 10th MMU translation register
  25. -- contains 0x1234 then the translated physical address would be 0x1234BCD.
  26. --
  27. -- My MMU is programmed using 8 I/O ports in the range 0xF8 through 0xFF. The
  28. -- chip select line is asserted for any access in that range and the MMU
  29. -- decodes the low three bits to determine which register is being accessed.
  30. --
  31. -- The port at 0xF8 is effectively a mux which selects the function of ports
  32. -- 0xFB through 0xFF.
  33. --
  34. -- Writing 0x00 through 0x0F to the function register at 0xF8 allows you to
  35. -- read/write one of the 16 MMU translation registers. With these selected;
  36. -- - 0xFC contains the high byte of the physical address
  37. -- - 0xFD contains the low byte of the physical address
  38. -- - 0xFB contains permission bits (read/write/execute, currently ignored)
  39. --
  40. -- So updating a mapping generally requires just three I/O writes: One to 0xF8
  41. -- to select which frame to modify, and one each to 0xFC and 0xFD to write out
  42. -- the new translation. The permission bits are programmable but currently
  43. -- ignored (I had planned to add some level of memory protection to UZI one day)
  44. --
  45. -- The "17th page" is a bit of a hack bolted on. The lazy programmer in me finds
  46. -- it much more convenient to sometimes access memory without remapping a frame;
  47. -- in particular you don't have to select which frame to remap such as to avoid
  48. -- remapping the memory pointed to by PC, SP or your source/target pointer.
  49. --
  50. -- Writing 0xFF to port 0xF8 selects the "17th page pointer". This is a 26-bit
  51. -- register but again could be wider/narrower as required. With this selected
  52. -- ports 0xFC, 0xFD, 0xFE, 0xFF are the register contents (with the high byte in
  53. -- 0xFC, low byte in 0xFF).
  54. --
  55. -- When the CPU reads or writes the I/O port 0xFA the MMU translates the I/O
  56. -- operation into a memory operation. The physical memory address accessed is
  57. -- the address contained in the 17th page pointer register. After the memory
  58. -- operation completes the 17th page pointer register is incremented so that
  59. -- repeated accesses to 0xFA walk forward through memory.
  60. --
  61. --
  62. -- MMU registers:
  63. --
  64. -- base = 0xF8 in standard socz80 system
  65. --
  66. -- base+0 mux frame select (write 00..0F to select frame, FF to select "17th page" pointer)
  67. -- base+1 (unused)
  68. -- base+2 17th page (read/write will address pointed memory and post-increment the pointer)
  69. -- base+3 page permissions
  70. -- base+4 page address (high byte) / ptr address (high byte)
  71. -- base+5 page address (low byte) / ptr address
  72. -- base+6 ptr address
  73. -- base+7 ptr address (low byte)
  74. --
  75. -- Basic operation of the MMU (older documentation, concise but still correct)
  76. -- For memory requests:
  77. -- The top 4 bits of CPU logical address are replaced with 14 bits taken
  78. -- from the MMU translation table entry indexed by those top four bits.
  79. -- For IO requests:
  80. -- Reads or writes to I/O port at (base+2) are converted into memory
  81. -- requests to the address pointed to by "the 17th page", a pointer which
  82. -- can be accessed by writing 0xFF to the frame select register (base+0)
  83. -- and then programming a 26-bit address into I/O ports FC FD FE FF. Each
  84. -- I/O request to port F/A results in the pointer being post-incremented
  85. -- by 1, which means that INIR our OUTIR instructions can read/write blocks
  86. -- of memory outside of the CPU logical address space through this port.
  87. --
  88. -- Note that the MMU has to insert a forced CPU wait state to give the
  89. -- addressed memory device time to read the new address off the bus.
  90. --
  91. library IEEE;
  92. use IEEE.std_logic_1164.all;
  93. use IEEE.numeric_std.all;
  94. entity MMU is
  95. port(
  96. clk : in std_logic;
  97. reset : in std_logic;
  98. address_in : in std_logic_vector(15 downto 0);
  99. address_out : out std_logic_vector(25 downto 0);
  100. cpu_data_in : in std_logic_vector(7 downto 0);
  101. cpu_data_out : out std_logic_vector(7 downto 0);
  102. cpu_wait : out std_logic;
  103. req_mem_in : in std_logic;
  104. req_mem_out : out std_logic;
  105. req_io_in : in std_logic;
  106. req_io_out : out std_logic;
  107. io_cs : in std_logic;
  108. req_read : in std_logic;
  109. req_write : in std_logic;
  110. access_violated : out std_logic
  111. );
  112. end MMU;
  113. architecture behaviour of MMU is
  114. -- each MMU entry looks like this
  115. type mmu_entry_type is
  116. record
  117. frame: std_logic_vector(13 downto 0);
  118. can_read: std_logic;
  119. can_write: std_logic;
  120. end record;
  121. -- the whole MMU state looks like this
  122. type mmu_entry_array is array(natural range <>) of mmu_entry_type;
  123. -- and here's our instance
  124. signal mmu_entry : mmu_entry_array(0 to 15);
  125. -- 17th page pointer (frame FF)
  126. signal mmu_frame_ff_pointer : std_logic_vector(25 downto 0);
  127. -- IO interface
  128. signal cpu_entry_select : std_logic_vector(7 downto 0);
  129. -- break up the incoming virtual address
  130. alias frame_number : std_logic_vector( 3 downto 0) is address_in(15 downto 12);
  131. alias page_offset : std_logic_vector(11 downto 0) is address_in(11 downto 0);
  132. signal map_io_to_ff_ptr : std_logic;
  133. signal was_map_io_to_ff_ptr : std_logic := '0';
  134. begin
  135. map_io_to_mem_proc: process(address_in, req_mem_in, req_io_in)
  136. begin
  137. if req_mem_in = '0' and req_io_in = '1' and address_in(7 downto 0) = "11111010" then
  138. map_io_to_ff_ptr <= '1';
  139. else
  140. map_io_to_ff_ptr <= '0';
  141. end if;
  142. end process;
  143. with map_io_to_ff_ptr select
  144. address_out <=
  145. mmu_entry(to_integer(unsigned(frame_number))).frame & page_offset when '0',
  146. mmu_frame_ff_pointer when '1';
  147. with map_io_to_ff_ptr select
  148. req_mem_out <=
  149. req_mem_in when '0',
  150. '1' when '1';
  151. with map_io_to_ff_ptr select
  152. req_io_out <=
  153. req_io_in when '0',
  154. '0' when '1';
  155. with map_io_to_ff_ptr select
  156. access_violated <=
  157. req_mem_in and ((req_read and not mmu_entry(to_integer(unsigned(frame_number))).can_read) or
  158. (req_write and not mmu_entry(to_integer(unsigned(frame_number))).can_write)) when '0',
  159. '0' when '1';
  160. -- force CPU to wait one cycle when we map IO to memory access; this
  161. -- is in order to give our synchronous memories a cycle to read the
  162. -- address, look up the data in synchronous memory, and provide a result.
  163. cpu_wait <= map_io_to_ff_ptr and (not was_map_io_to_ff_ptr);
  164. data_out: process(address_in, cpu_entry_select, mmu_entry, mmu_frame_ff_pointer)
  165. begin
  166. if cpu_entry_select = "11111111" then
  167. -- pointer (FF)
  168. case address_in(2 downto 0) is
  169. when "000" =>
  170. cpu_data_out <= cpu_entry_select;
  171. when "011" =>
  172. cpu_data_out <= "00000011";
  173. when "100" =>
  174. cpu_data_out <= "000000" & mmu_frame_ff_pointer(25 downto 24);
  175. when "101" =>
  176. cpu_data_out <= mmu_frame_ff_pointer(23 downto 16);
  177. when "110" =>
  178. cpu_data_out <= mmu_frame_ff_pointer(15 downto 8);
  179. when "111" =>
  180. cpu_data_out <= mmu_frame_ff_pointer(7 downto 0);
  181. when others =>
  182. cpu_data_out <= "00000000";
  183. end case;
  184. else
  185. -- 16 frames (00 .. 0F)
  186. case address_in(2 downto 0) is
  187. when "000" =>
  188. cpu_data_out <= cpu_entry_select;
  189. when "011" =>
  190. 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;
  191. when "100" =>
  192. cpu_data_out <= "00" & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8);
  193. when "101" =>
  194. cpu_data_out <= mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
  195. when others =>
  196. cpu_data_out <= "00000000";
  197. end case;
  198. end if;
  199. end process;
  200. mmu_registers: process(clk)
  201. begin
  202. if rising_edge(clk) then
  203. if reset = '1' then
  204. mmu_entry( 0).frame <= "10000000000000"; -- first page of SRAM (monitor ROM)
  205. mmu_entry( 1).frame <= "00000000000001"; -- DRAM page 1
  206. mmu_entry( 2).frame <= "00000000000010"; -- DRAM page 2
  207. mmu_entry( 3).frame <= "00000000000011"; -- DRAM page 3
  208. mmu_entry( 4).frame <= "00000000000100"; -- DRAM page 4
  209. mmu_entry( 5).frame <= "00000000000101"; -- DRAM page 5
  210. mmu_entry( 6).frame <= "00000000000110"; -- DRAM page 6
  211. mmu_entry( 7).frame <= "00000000000111"; -- DRAM page 7
  212. mmu_entry( 8).frame <= "00000000001000"; -- DRAM page 8
  213. mmu_entry( 9).frame <= "00000000001001"; -- DRAM page 9
  214. mmu_entry(10).frame <= "00000000001010"; -- DRAM page 10
  215. mmu_entry(11).frame <= "00000000001011"; -- DRAM page 11
  216. mmu_entry(12).frame <= "00000000001100"; -- DRAM page 12
  217. mmu_entry(13).frame <= "00000000001101"; -- DRAM page 13
  218. mmu_entry(14).frame <= "00000000001110"; -- DRAM page 14
  219. mmu_entry(15).frame <= "10000000000001"; -- second page of SRAM
  220. mmu_entry( 0).can_read <= '1';
  221. mmu_entry( 1).can_read <= '1';
  222. mmu_entry( 2).can_read <= '1';
  223. mmu_entry( 3).can_read <= '1';
  224. mmu_entry( 4).can_read <= '1';
  225. mmu_entry( 5).can_read <= '1';
  226. mmu_entry( 6).can_read <= '1';
  227. mmu_entry( 7).can_read <= '1';
  228. mmu_entry( 8).can_read <= '1';
  229. mmu_entry( 9).can_read <= '1';
  230. mmu_entry(10).can_read <= '1';
  231. mmu_entry(11).can_read <= '1';
  232. mmu_entry(12).can_read <= '1';
  233. mmu_entry(13).can_read <= '1';
  234. mmu_entry(14).can_read <= '1';
  235. mmu_entry(15).can_read <= '1';
  236. mmu_entry( 0).can_write <= '0';
  237. mmu_entry( 1).can_write <= '1';
  238. mmu_entry( 2).can_write <= '1';
  239. mmu_entry( 3).can_write <= '1';
  240. mmu_entry( 4).can_write <= '1';
  241. mmu_entry( 5).can_write <= '1';
  242. mmu_entry( 6).can_write <= '1';
  243. mmu_entry( 7).can_write <= '1';
  244. mmu_entry( 8).can_write <= '1';
  245. mmu_entry( 9).can_write <= '1';
  246. mmu_entry(10).can_write <= '1';
  247. mmu_entry(11).can_write <= '1';
  248. mmu_entry(12).can_write <= '1';
  249. mmu_entry(13).can_write <= '1';
  250. mmu_entry(14).can_write <= '1';
  251. mmu_entry(15).can_write <= '1';
  252. mmu_frame_ff_pointer <= "10000000000000000000000000"; -- map first byte of ROM to pointer on reset
  253. was_map_io_to_ff_ptr <= '0';
  254. else
  255. was_map_io_to_ff_ptr <= map_io_to_ff_ptr;
  256. if io_cs = '1' and req_write = '1' then
  257. case address_in(2 downto 0) is
  258. when "000" =>
  259. cpu_entry_select <= cpu_data_in;
  260. when "011" =>
  261. if cpu_entry_select(7 downto 4) = "0000" then
  262. mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_read <= cpu_data_in(0);
  263. mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_write <= cpu_data_in(1);
  264. end if;
  265. when "100" =>
  266. if cpu_entry_select(7 downto 4) = "0000" then
  267. mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
  268. cpu_data_in(5 downto 0) & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
  269. elsif cpu_entry_select = "11111111" then
  270. mmu_frame_ff_pointer <=
  271. cpu_data_in(1 downto 0) & mmu_frame_ff_pointer(23 downto 0);
  272. end if;
  273. when "101" =>
  274. if cpu_entry_select(7 downto 4) = "0000" then
  275. mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
  276. mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8) & cpu_data_in(7 downto 0);
  277. elsif cpu_entry_select = "11111111" then
  278. mmu_frame_ff_pointer <=
  279. mmu_frame_ff_pointer(25 downto 24) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(15 downto 0);
  280. end if;
  281. when "110" =>
  282. if cpu_entry_select = "11111111" then
  283. mmu_frame_ff_pointer <=
  284. mmu_frame_ff_pointer(25 downto 16) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(7 downto 0);
  285. end if;
  286. when "111" =>
  287. if cpu_entry_select = "11111111" then
  288. mmu_frame_ff_pointer <=
  289. mmu_frame_ff_pointer(25 downto 8) & cpu_data_in(7 downto 0);
  290. end if;
  291. when others =>
  292. -- nothing
  293. end case;
  294. elsif map_io_to_ff_ptr = '0' and was_map_io_to_ff_ptr = '1' then
  295. -- post-increment our pointer (this is what makes "the 17th page" efficient!)
  296. mmu_frame_ff_pointer <= std_logic_vector(unsigned(mmu_frame_ff_pointer) + 1);
  297. end if;
  298. end if;
  299. end if;
  300. end process;
  301. end;