Replicating the 74LS670 shouldn't be a problem to replicate in HDL with nothing more than a logic array: x bits by x words.
Feeding bank address bits position into that logic array, where the logic data output is wired to the upper address of the DDR3 controller to address all your ram bank by bank should also be easy.
Reset clears the array to 0,1,2,3.... to feed through the full Z80 upper address bits, 64k uninterrupted.
Also, I assume you have a write data or port into the array to fill the upper address for each bank group.
IE: to get the full rw addr:
// register to hold 16 banks of 4096 words each with an extra 16 address bits.
// (This is a 256 megabyte example)
logic [15:0] page_remap [0:15] = '{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} ; // Default passes through the Z80's upper address bits untouched.
// Drive the DDR3 address with the stored banks inserted above the first 12 address bits.
assign DDR3_addr[31:0] = {4'd0, page_remap[z80_addr[15:12]], z80_addr[11:0] };
// To set a bank :
if (reset) page_remap <= '{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} ; // After reset, everything runs as a direct address feed-through.
else if (port_data_bankset_pulse) page_remap[port_data_bankset[3:0]] <= {port_data_bankadr_h,port_data_bankadr_l} ;
If so, how is this so difficult to implement?
Obviously, you can parameterize the width and address bit locations, like making each bank size like 4 banks of 16k instead of 16 banks of 4k, leaving you with 1gbyte addressable space.
Also, you can make 2x page_remap arrays. One for write data, and one for read data.
You can also pipe out an asynchronous EA bus with 1 line:
EA_bus <= page_remap[z80_addr[15:12]];
generating your FPGA based MMU upper address bits for external use, just don't forget to move the address bits on your peripherals.
You can go extra dumb by just using 32 ports, every 2 ports being 16 bit word of your 'page_remap' saving Z80 cpu cycles when filling the MMU table. The 'page_remap' could also be 32 bytes of writable ram, but this will leave a hole in your address space as the MMU needs access no matter what it's contents are whereas ports wont. Also remember, you can read as well as write ports. Just tie the 32 port outputs into 32 port inputs of the same port number.
IE:
wire [15:0] page_remap [0:15] = '{ // assign 16 banks of 4kb
{port0,port1},
{port2,port3},
{port4,port5},
{port6,port7},
{port8,port9},
{port10,port11},
{port12,port13},
{port14,port15},
{port16,port17},
{port18,port19},
{port20,port21},
{port22,port23},
{port24,port25},
{port26,port27},
{port28,port29},
{port30,port31}};
or:
IE:
wire [15:0] page_remap [0:3] = '{ // assign 4 banks of 16kb
{port0,port1},
{port2,port3},
{port4,port5},
{port6,port7}};
Only now your reset needs to be setting ports #0-31 or 0-7.
Dont forget you can also have a separate page_remap for data and another for OP-code since our Z80 bus already decodes the op-code VS data.