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: figure 1

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!

figure 2

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.