Foreword
In the previous post we managed to get the fx68k core to run on a Basys 3 board, and execute a very simple machine code program.
In this post we will continue our journey in getting an Amiga core to run on a Zybo board. Having said that, in this post we will again do the exercise on the Basys 3 board. We will be finally moving to the Zybo board in the next post.
As mentioned in a previous post, we will be scrutinising the Mini Amiga project, here, that will form the basis of our Amiga exercise.
The Mini Amiga project is based on a Altera FPGA, and for that reason we need to scrutinise the code of this project. However, this makes the journey so much more exciting.
A bit of background on the Mini Amiga project
If one reads this article on Wikipedia, one can see that the original Mini Amiga project was based on a physical Motorola 68000 CPU, and the Amiga chipset was implemented within an FPGA.
Since then, the source code of the Amiga Project was adjusted so it can be used within the main MiSTer project.
To understand the source code of the Mini Amiga Project, a good starting point would be to look at the file rtl/minimig.v. This file used to be the top level module for the original Mini Amiga project. Looking at the inputs/outputs of this module, it is immediately obvious that this module doesn't host a 68000 implementation, and need to be connected externally.
To cater for an on-FPGA implementation for the 68000, a couple of wrappers were created around the minimig module. More on this in the next section.
A deeper look into the source code
In the previous section I mentioned that rtl/minimig.v used to be the top level module for the original implementation for the Mini Amiga project, and that wrappers were written to interface it with an on-FPGA 68000 implementation.
Let us have a closer look at these wrappers. We start by looking into the file Minimig.sv, which is located in the root. This file hosts a module called emu, which in turn instantiates an instance of the minimig module.
Something else that is also interesting of the module emu, is that it instantiates an instance of cpu_wrapper, and it is here where we actually create an instance of the fx68k core and linking up the ports to the corresponding minimig instance.
The rest of the wrapper code gets very specific to the features of the DE10-Nano board.
For our goal of implementing an Amiga on a Zybo board, we will be creating our own wrappers around the Minimig module.
Pruning the Minimig module
When porting a complex project from one platform to another, a lot of times it is required to have an in-depth understanding of how the system works.
This is easier said than done, especially if it is not possible to play with the system on the original platform.
In our case, we have a similar scenario. Your preferred FPGA board for implementing the Mini Amiga will probably not be the DE-10 Nano board, for which the current project is written for. Also, buying the DE-10 Nano board to gradually understand the components of the Mini Amiga project, before moving to your actual choice of FPGA board, doesn't sound like an economically viable option either.
To get around the problem of complexity, I will be stripping down the Minimig module to its most basic form, and then re-adding functionality as we go along.
We start by looking at the ports of the Minimig module, which are grouped as follows:
- m68k pins
- sram pins
- system pins
- rs232 pins
- I/O
- host controller interface (SPI)
- Video
- RTG Framebuffer control
- audio
- user/io
- fifo/track display
In our first round, we will only be using the first three group of ports, which are m68k pins, sram pins and sram pins. The rest of the ports we will remove or comment out.
Next, let us have a look inside the minimig module, as which instances we can remove. I have found the following to remove:
- userio
- rtg
Modifying top.v
module top( ... ); ... amiga_clk amiga_clk ( .clk_28(clk_28mhz), // 28MHz output clock ( 28.375160MHz) .clk7_en(clk7_en), // 7MHz output clock enable (on 28MHz clock domain) .clk7n_en(clk7n_en), // 7MHz negedge output clock enable (on 28MHz clock domain) .c1(c1), // clk28m clock domain signal synchronous with clk signal .c3(c3), // clk28m clock domain signal synchronous with clk signal delayed by 90 degrees .cck(cck), // colour clock output (3.54 MHz) .eclk(eclk), // 0.709379 MHz clock enable output (clk domain pulse) .reset_n(~(button_reset)) ); ... endmoduleHere is another mystery. The amiga_clk module wants a 28MHz clock input, whereas in the previous post we have defined a clock of 16MHz for clocking our CPU.
The resulting fx68k and minimig instance look as follows:
... fx68k fx68k( .clk(clk_28mhz), .HALTn(1), // Used for single step only. Force high if not used // input logic HALTn = 1'b1, // Not all tools support default port values // These two signals don't need to be registered. They are not async reset. .extReset(reset_cpu_in), // External sync reset on emulated system .pwrUp(reset_cpu_in), // Asserted together with reset on emulated system coldstart .enPhi1(phi), .enPhi2(~phi), // Clock enables. Next cycle is PHI1 or PHI2 .eRWn(read_write), .oRESETn(reset_cpu_out), .ASn(As), .LDSn(Lds), .UDSn(Uds), .DTACKn(data_ack), .VPAn(1), .BERRn(1), .BRn(1), .BGACKn(1), .IPL0n(interrupts[0]), .IPL1n(interrupts[1]), .IPL2n(interrupts[2]), .iEdb(data_in), .oEdb(data_out), .eab(add) ); ... minimig minimig ( //m68k pins .cpu_address(add), // m68k address bus .cpu_data(data_in), // m68k data bus .cpudata_in(data_out), // m68k data in ._cpu_ipl(interrupts), // m68k interrupt request ._cpu_as(As), // m68k address strobe ._cpu_uds(Uds), // m68k upper data strobe ._cpu_lds(Lds), // m68k lower data strobe .cpu_r_w(read_write), // m68k read / write ._cpu_dtack(data_ack), // m68k data acknowledge ._cpu_reset(reset_cpu_in), // m68k reset ._cpu_reset_in(reset_cpu_out),//m68k reset in .nmi_addr(0), // m68k NMI address //sram pins .ram_data(), // sram data bus .ramdata_in(22), // sram data bus in .ram_address(ram_add), // sram address bus ._ram_bhe(), // sram upper byte select ._ram_ble(), // sram lower byte select ._ram_we(), // sram write enable ._ram_oe(), // sram output enable .chip48(), // big chipram read //system pins .rst_ext(), // reset from ctrl block .rst_out(), // minimig reset status .clk(clk28mhz), // 28.37516 MHz clock .clk7_en(clk7_en), // 7MHz clock enable .clk7n_en(clk7n_en), // 7MHz negedge clock enable .c1(c1), // clock enable signal .c3(c3), // clock enable signal .cck(cck), // colour clock enable .eclk(eclk), // ECLK enable (1/10th of CLK) ); ...
Building and testing
// generate a keystrobe which is valid exactly one clk cycle always @(posedge clk) begin reg kms_levelD; if (clk7n_en) begin kms_levelD <= kms_level; keystrobe <= (kms_level ^ kms_levelD) && (kbd_mouse_type == 2); end endIn this case, the register kms_levelD is a local register. To make Vivado happy, you will need to move the register declaration outside of the always block.
module minimig ( ... input button_reset, ... ); ... assign cpurst = button_reset; ... endmoduleAlso, our amiga_clk instance will need to use the _cpu_reset port of the minimig module:
module top( ... ); ... amiga_clk amiga_clk ( .clk_28(clk_28mhz), // 28MHz output clock ( 28.375160MHz) .clk7_en(clk7_en), // 7MHz output clock enable (on 28MHz clock domain) .clk7n_en(clk7n_en), // 7MHz negedge output clock enable (on 28MHz clock domain) .c1(c1), // clk28m clock domain signal synchronous with clk signal .c3(c3), // clk28m clock domain signal synchronous with clk signal delayed by 90 degrees .cck(cck), // colour clock output (3.54 MHz) .eclk(eclk), // 0.709379 MHz clock enable output (clk domain pulse) .reset_n(~(_cpu_reset)) ); ... endmoduleAnother signal with a similar issue was the halt signal. This signal is also controlled by the userio module, which can halt the cpu on demand.
Your blog series, this one and the one for C64, are both very interesting. Keep up the good work!
ReplyDelete