Found it!

Coping without SignalTap – Part 3 – 2022-04-28

I’ve found the bug which was preventing interrupts from working with the EightThirtyTwo CPU, at least in the EightThirtyTwoDemos projects…

… and of course, it was something simple and blindingly obvious.

Here’s how I tracked it down.

Firstly, I needed to instantiate the jcapture module that I talked about last time, in the IceSugarPro board-specific toplevel file, like so:

capblock : block
	signal capd : std_logic_vector(63 downto 0);
	signal capq : std_logic_vector(63 downto 0);
	signal capu : std_logic;
begin
	cap : entity work.jcapture
	port map(
		clk => clk_sys,
		reset_n => pll_locked,
		d => capd,
		q => capq,
		update => capu
	);

	capd <= trace;
	process(clk_sys) begin
		if rising_edge(clk_sys) then
			if capu='1' then
				capreset <=capq(0);
				led_red <= capq(1);
				led_green <= capq(2);
				led_blue <= capq(3);
			end if;
		end if;
	end process;
end block;

(I’m working in VHDL here, but I’m configuring the jcapture block’s width and depth using a VHDL package rather than generics, since Yosys doesn’t currently map from Verilog parameters to VHDL generics.)

The trace signal is also a 64-bit vector, the contents of which come from the virtual toplevel. The capreset signal contributes to the project’s global active-low-reset signal, and powers up in the low state, allowing me to release the reset from the host PC after setting up capture triggers.

I’ve tidied up the Tcl scripting, too, so now I can issue commands like:

virscan ecp5.tap setmask
vdrscan ecp5.tap 64 0xff
virscan ecp5.tap write
vdrscan ecp5.tap 64 1

to set up a trigger condition and release the initial reset. I can also fetch results from the FIFO in chunks, like so:

set a [vdrscan ecp5.tap 16 0 -start]
set b [vdrscan ecp5.tap 32 0 -cont]
set c [vdrscan ecp5.tap 16 0 -end]

which makes presenting the captured data a little easier. In the long term it’d be nice to emit a .vcd file from the captured data.

So having set up the jcapture instance and a Tcl script to set the trigger, release the core’s reset and dump the capture values to the terminal window, I passed the current Program Counter and the Interrupt flag from the CPU out through the virtual toplevel to the jcapture unit, and observed the results from the host PC.

I could see that the interrupt flag was never being triggered – so I added signals to monitor the inputs to the interrupt controller – which again were never being triggered. Most odd…

So next I monitored the address and write signals entering the timer controller, to verify that the timer setup was happening correctly and saw writes to register 0x7 and 0xb. That’s strange – the registers are all on 32-bit boundaries, so those addresses are clearly wrong.

Then it finally dawned on me what was going on:

The CPU is 32-bits, and while it supports accesses to arbitrary byte addresses, it handles the messy alignment details internally, so the rest of the system only ever sees accesses on 32-bit boundaries. Therefore its address bus isn’t (31 downto 0) – it’s (31 downto 2).

So the ultimate cause of my problem was that the lower two bits of the system’s address bus were left unconnected, and took whatever default value the synthesis tool felt like assigning to them! With Quartus and ISE this turns out be zero, and everything works as it should. With Yosys and friends, these two bits were taking a “1” value, causing address comparisons in the timer controller (and other peripherals) to fail, and thus the interrupts were never happening!

A very simple error which has lurked in the project for years. Finding it without some kind of JTAG probe would have been much more difficult, so I guess the time spent on the jcapture module was time well spent.

Leave a Reply

Your email address will not be published. Required fields are marked *