Your code mixes synchronous, asynchronous and clock (usages of) signals in a, more or less, liberal way. For example, in CARADDR.sv, signals BUSAK & INTAK are asynchronous, P2 is a clock and SR3 is used: asynchronously (for card_load_count); synchronously (for LD); and as a clock (for ADDR). Also, P2 is sometimes active on the rising edge, other times on the falling one. There's a block (at least, CARADDR.sv) with two asynchronous inputs (BUSAK & SR3 on the first always). I think all these is not exactly kosher now, although maybe it was back in the '70s, when using 74/4000 ICs.
Yeah I was working with the timing diagrams of the CP1600 CPU which is used by the system, an internal document detailing the behavior of the original chip, and the results of actual observation of its behavior. It's an ... interesting setup. As I think I mentioned in the code, the CPU is a 4-state machine where P2 rise is TS1, P2 fall is TS2, second rise is TS3, and second fall is TS4. The fun part is that this is the processor's state machine, and outside of the processor we would need some fuzzy logic to figure out which state it is in, if that is even necessary. In the case of this chip, it does not seem to be, as long as you are okay with the fact that P2 has 2 positive edges for the same bus operation.
So, for instance, the write to memory does not have valid data on the first pulse but does on the second. With the current code, the memory is written on both positive edges, but the correct value wins.
Anyway, what I do know is that the 3 control lines should be valid on the rising edge of P2 and throughout the high level.
This chip is a split-brained one, where the first half is mainly clocked by P2 and the other half is mainly clocked by SR3. SR3 is a level trigger for the purpose of enabling the SD outputs, but its edges also trigger a couple of internal changes. Rationale for the count: The graphics chip will ask the CPU for control of the bus (BUSRQ) early in an attempt to ensure that the CPU has time to honor the request before the graphics chip needs it. But that means it might still be in the middle of displaying that last line when the CPU relents. At that time, BUSAK will be active but loading of values should not begin yet. So there is the activation counter which is reset by SR3's pulses, ensuring that the next set of values do not begin to load until that line is finished. So the count should be reset by the edge of SR3, at least. But holding reset asynchronously is also an option. Maybe even the preference because the edges are about 8 clocks apart.
Similarly, the DTB condition is pretty much a level trigger for enabling DB as an output. The bus expects to see the value through both P2 pulses (in other words, 3 phase changes) while DTB is active, so once we are sure there is a DTB that we are supposed to respond to, we have to hold it until the condition changes.
There are register w/o any means to initialize them asynchronously (albeit for some people, or in some scenarios, this may not be an issue).
Yeah this chip will be running constantly because it's part of the video display system. The 20-position shift register represents the background tiles for every 16 lines (8 lines per tile, but doubled because of the TV's interlace). The graphics chip that works along with this chip initiates the load sequence of the background tiles from its internal memory, 20 at a time, then repeats those 20 for the next 7 lines, then makes it load the next 20, and so on, until it has filled the screen. Then the graphics chip sends an interrupt request to the CPU which responds with an INTAK. We see the INTAK and reset the address while entering CPU mode.
Anyway, because of that, it should only take 1 cycle to get everything into a known state, so it would likely not be noticeable. That is probably why the original designer didn't add a reset pin, even though the other chips have one.
The next is a matter of taste, but a little of indentation can help to ascertain what is the asynchronous part of a block from the syncronous one:
always@(negedge SR3 or posedge INTAK)
begin
if(INTAK) begin
ADDR <= 0;
end else if(LD) begin
ADDR <= ADDR + 1;
end
end
vs.
always@(posedge INTAK, negedge SR3) begin
if (INTAK) begin
ADDR <= 0;
end else
if (LD) begin
ADDR <= ADDR + 1;
end
end
I can see either argument. My personal preference (both in general programming and in this) is that if it's a sequence of conditions rather than a hierarchy, I prefer to keep the indenting the same. One could argue that it IS a hierarchy, and you are technically correct. But logically, it's just a list of conditions where the first one that's true wins. It's logically closer to a case statement. This is probably why some languages added the syntatic sugar of "elseif".
Also, putting the clock (P2) inside if conditions (this happens in the 1st always block of CARADDR.sv) drives one to think that it is not a clock.
I'll have to review my rationale for this block, and consider whether or not it's still valid (may be a leftover from my crash course in Verilog).
This is my first time designing for an FPGA, and learning Verilog from zero. So I'd appreciate any constructive criticism on coding, style, etc.
How much experience do you have doing synchronous digital logic design?
I guess this is your way of saying "What a mess of spaghetti code"
Some experience, but definitely not up to modern standards. I understand edge and level triggering, at least, and I am conscious of propagation delay.
But the device that I'm duplicating was designed before modern design disciplines were established, so some of the odd triggering you might be noticing is unfortunately necessary.