on
Retro Challenge 2024 #2
Introduction
The first thing to do is to get a 68000 core running in simulation. There is such a core on Opencores.org which is used in various projects.
Setting up the simulation toolchain
The github repository is for the GW68000 system is here.
I use GDHL for VDHL simualtion and GTKWave for waveform display. Both have served me well in the past and work well on Linux. There are also Windows versions but YMMV.
Because TG68 uses old-style Synopsys VHDL code (ieee.std_logic_unsigned
), GHDL must be invoked with the -fsynopsys
and -fexplicit
flags, see ghdl/run.sh
.
Simulation results
The 68000 user manual states the following:
When RESET and HALT are driven by an external device, the entire system, including the processor, is reset. Resetting the processor initializes the internal state. The processor reads the reset vector table entry (address 0x00000) and loads the contents into the supervisor stack pointer (SSP). Next, the processor loads the contents of address 0x00004 (vector table entry 1) into the program counter. Then the processor initializes the interrupt level in the status register to a value of seven.
The GHDL testbench simulation waveforms show this behaviour after the reset line has been released:
As there is no memory connected to the core and the data bus is connected to all zeros, the reset vector and stack location are read as 0. The CPU loads 0x00000000 into the program counter and start executing from there.
Extending the testbench
Now that the TG68 core seems to be working, it’s time to add some simulated RAM to GW68000 top level. This means we need an address decoder, and a simulation model for a RAM. There are many types of RAM (static RAM, dynamic RAM, synchronous dynamic RAM), but most FPGAs contain small RAMs called registered block RAM. These are static RAMs with a twist: they require a clock signal and the data appears at the output of the RAM after one clock cycle.
Because the 68000 takes multiple cylces to read and write the memory, a single clock cycle delay is fine.
There are two ways to code a block RAM for an FPGA. The first is to use the vendor-specific entitiy name for a block RAM and the synthesis tool will instantiate the RAM directly. The second is more generic and does not depend on a specific vendor; a block RAM is coded in such a way that the synthesis tools recognise the RAM behaviour and infer a vendor-specific block. Most synthesis tool manuals will tell you how to code up a generic block RAM so they will reliably recognise it.
In my VHDL model, the RAMs can be initialised at startup using a file name. There are two RAM blocks, one upper and one lower RAM, one for each byte in a 16-bit word. The RAM content files are boot_lower.txt
and boot_upper.txt
.
RAM contents
Using the asmx assembler, I made an endless loop to use as the RAM contents.
000000 .org $0000
000000 00000200 dl $00000200
000004 00000008 dl $00000008
000008 LOOP:
000008 4EF8 0008 jmp LOOP
The first two 32-bit words of the RAM are the supervisor stack and the reset vector.
The documentation for the assembler is here.
The listing above is implemented by the two RAM content files:
# 256 byte upper RAM for GW68000
# SSP = 0x00000200
# PC = 0x00000008
00
02
00
00
4E
00
# 256 byte lower RAM for GW68000
# SSP = 0x00000200
# PC = 0x00000008
00
00
00
08
F8
08
Now, the testbench simulation shows the cpu vectoring to address 0x00000008 and looping!
Conclusion
The TG68 vhdl implementation seems to work in the simulator. It reads the contents fo the added block RAMs correctly. I still need to test writing to RAM.
All changes have been pushed to Github.