Getting a Verilog PS/2 interface working - Page 1
Forum section: electronics / general

author: nockieboy
author_rank: Super Contributor
timestamp: June 19, 2020, 04:07:12 pm
content: Hi everyone,I'm trying to add a PS/2 interface to my Intel/Altera EP4CE10-based video card for my DIY 8-bit computer. I've been having difficulty getting a working PS/2 interface up and running for ages, but have now bitten the bullet and need to deal with these issues as the video-card side of the project is completed (at least in version 1).I've had a hunt around online and found various projects, none of which seem to do what I want - basically I just need an interface that will take the PS/2 serial data and convert it into ASCII characters to pass to the interface that connects to the Z80 host. I am trying to use the two files attached to do this - keyboard.sv should translate the PS/2 data into a keycode and pass it to key2ascii.sv, which translates the keycode into an ASCII char and passes that out via an 8-bit parallel bus to the Z80 interface.I'm having difficulty even getting the code to compile, though. At the moment, I'm getting these warnings during synthesis:Code:Warning (13012): Latch key2ascii:inst26|ascii_code has unsafe behaviorWarning (13013): Ports D and ENA on the latch are fed by the same signal keyboard:inst25|ps2_rx:ps2_rx_unit|d_regNow I'm not very experienced with HDL (be that Verilog or VHDL) and I'm struggling to solve this issue. It seems the code is a bit out anyway - originally the if conditionals in the always @* block only had a single &, so I'm guessing there's more errors in this code.I've added the `initial begin` block myself, in an attempt to stop these warnings having checked out various posts about similar warnings, but that's done nothing.When it runs, I'm just getting a stream of 'x's and non-printable characters in the serial console - with no keyboard plugged in, it shouldn't be showing anything, so I'm wondering if anyone out there with a lot more knowledge and experience than me can help cast a light on this, please?

author: nockieboy
author_rank: Super Contributor
timestamp: June 19, 2020, 04:14:32 pm
content: Hang on - think I've fixed the errors:Code:ascii_code = 8'h00;Just needed to add that at the start of the 'always @*' block - seems the initial block wasn't quite making Quartus happy enough. On to the next step of debugging... watch this space!

author: Bassman59
author_rank: Super Contributor
timestamp: June 19, 2020, 05:35:05 pm
content: Quote from: nockieboy on June 19, 2020, 04:14:32 pmThe initial block in Verilog got hijacked into use as a start-up reset.Now without seeing your code, only the quote above, it appears as if you wrote a combinatorial process. That's fine. But in a combinatorial process, all left-hand sides must get an assignment under all conditions, otherwise a latch is inferred to maintain the state of the signal. And in FPGAs, the inference and use of latches is greatly discouraged.Why aren't you writing synchronous logic?

author: asmi
author_rank: Super Contributor
timestamp: June 19, 2020, 05:35:40 pm
content: You have the classic latch in your code. Think about what ascii_code is going to be when scan_code_ready is low (0)?Remember the rule of combinatorial logic - ALL output signals of "always @*" block have to be defined AT ALL TIMES, with all possible combinations of input signals covered.Also - I noticed you use SystemVerilog, if so I highly recommend using always_ff and always_comb blocks for sequential and combinatorial blocks respectively.

author: james_s
author_rank: Super Contributor
timestamp: June 19, 2020, 06:02:19 pm
content: If you're using Verilog code why are you messing with the schematic entry? Just use either Verilog or VHDL for the top level too, it ends up being much simpler and easier to work with.

author: nockieboy
author_rank: Super Contributor
timestamp: June 19, 2020, 06:26:02 pm
content: Thanks for the replies - that's not my code, by the way, but an example I found on the interweb somewhere. The use of 'always @*' confused the heck out of me - as you know asmi, I'm a learner when it comes to HDL and suddenly walking into code that is combinatorial instead of synchronous was a bit of a shock.I had to do some reading on it and still don't really understand it.I don't suppose there's any reason I couldn't re-write the key2ascii.sv code and make it synchronous? It'd certainly sit a lot better with my understanding, if nothing else.Getting these two modules working is part of the task, but I've also found a major issue with handling the IO request from the Z80 host to get a character that has been typed.How it should work is thatkeyboard.svgets the keycode when a key is pressed, passes it tokey2ascii.svwhich decodes it into an 8-bit ASCII character value and passes that toZ80_bridge.sv, which updates the PS2_CHAR register with that value. PS2_CHAR remains unchanged, until the Z80 host reads it via an IO operation, or another ASCII character is sent from key2ascii.sv. I have code on the Z80 polling the PS2_CHAR register constantly - if the returned value is 0x00, it does nothing, otherwise it prints the character to the serial console. Once the character is read, Z80_bridge.sv resets PS2_CHAR to 0x00 again to prevent repetition. This should all happen so quickly there's no chance of a repeated (or held) keypress overwriting the last one before it's read.@BrianHG, if you're reading this, I'd certainly appreciate your critical eye on the code in Z80_bridge.sv. I think I'm messing up the timings on the IO cycle and either not getting the data onto the Z80's bus in time, or not holding it on the bus long enough for the Z80 to read, asI'm getting a constant stream of 'x's in the console, with nothing attached to PS2_DAT and PS2_RDY grounded on the Z80_bridge's inputs, so this problem has nothing to do with the PS2 modules, ironically. It's probably some stupid mistake I've made in the code when I created the code to manage an IO read.The Z80_bridge should be returning 0x00 if there is no character value in the buffer,which should be the case all the timeas keyboard.sv or key2ascii.sv modules aren't connected to it and the PS2_RDY input is grounded.Quote from: james_s on June 19, 2020, 06:02:19 pmWhat you see is just a small part of a very complicated (to me, at least) HDL project to handle video graphics and interface that with a Z80 via an 8-bit parallel interface. I find using schematic entry makes it all much easier to understand at a glance - I'm not experienced with this stuff at all and am very much a learner.

author: BrianHG
author_rank: Super Contributor
timestamp: June 19, 2020, 10:46:22 pm
content: I'll take a look at the code on the weekend, however, try tying the data outputs, before and after the key decoder, to a few spare inputs on the RS232 debugger 'debug' input ports and let me know what you see when running the debugger on the utility inputs.Also, add 2x8 bit counters on the same keyboard clock, and tie the 'scan_code_ready' and 'ascii_code_ready' outputs to each counter's enable input. Tie those 8 bit outputs to the other 2 spare utility inputs on the RS232 debugger module and see what happens when you use the keyboard.See if the counters increment once per key press.

author: 0db
author_rank: Frequent Contributor
timestamp: June 20, 2020, 09:08:56 am
content: Quote from: nockieboy on June 19, 2020, 04:07:12 pmHow to manage special keys like shift, caps, ctrl, F1-12, etc?They cannot be mapped as ASCII char.

author: 0db
author_rank: Frequent Contributor
timestamp: June 20, 2020, 09:26:30 am
content: Quote from: nockieboy on June 19, 2020, 06:26:02 pmBad idea. I suggest a FIFO and status register with some dedicated bits. buffer empty, buffer full, buffer error. The FIFO buffer automatically flushes away a char once it's read.

author: nockieboy
author_rank: Super Contributor
timestamp: June 20, 2020, 10:07:08 am
content: Quote from: BrianHG on June 19, 2020, 10:46:22 pmThank you.I'll give those debugging efforts a go later, but for the moment my efforts are purely focused ongetting the z80_bridge interface working with IO readsfirst - for all I know, the PS/2 interface may be working perfectly, but until I can get the z80_bridge returning the value in the PS2_CHAR register in response to a Z80 IO read, the PS/2 interface itself is a moot point.I've currently got the PS/2 modules completely disconnected from the z80_bridge, with the PS2_RDY input to the z80_bridge grounded (see schematic below), so there should be no data going in to the PS2_CHAR register in the z80_bridge - and the z80_bridge should be returning 0x00 whenever an IO read from the Z80 accesses it. Instead, I'm getting a constant stream of 'x's (0x78) values from the z80_bridge, and I can't figure out why.Quote from: 0db on June 20, 2020, 09:08:56 amThere's a State Machine inkeyboard.svthat handles shift, caps and possibly other keys too. I'm not worried about function keys or any of the other non-ASCII keysjust yet- that's something I can work on and add to later (only requires a modification tokey2ascii.svto pass the scan_code out to the host Z80 - code running on the Z80 can interpret the non-ASCII chars however it wants.) As for your suggestion for a buffer - well, yes, that's probably a good idea but could be overkill when the auto-repeat timing on a keyboard is waaaay longer than the time it takes for the Z80 to poll the IO port and read the PS2_CHAR register. If it turns out that I'm dropping keys, then I'll add a buffer in later - but as I've said above, my focus has shifted away from the PS/2 interface for the moment and is focused on getting the IO interface with the Z80 working first.

author: BrianHG
author_rank: Super Contributor
timestamp: June 20, 2020, 01:00:05 pm
content: How do you handle keys being held down, like holding the up arrow for walking forward in a video game.Or multiple keys simultaneously being pressed...

author: nockieboy
author_rank: Super Contributor
timestamp: June 20, 2020, 04:05:24 pm
content: Quote from: BrianHG on June 20, 2020, 01:00:05 pmNo idea - haven't gotten that far yet. I can't do anything all the time I can't read what key is being pressed or all I can read is 0x78.If it turns out the key2ascii.sv module doesn't cater for all my needs, I'll have to have a think about some other way to do it. Either way though, I need to be able to reliably do an IO read from the z80_bridge.

author: asmi
author_rank: Super Contributor
timestamp: June 20, 2020, 04:39:00 pm
content: Quote from: nockieboy on June 20, 2020, 04:05:24 pmI think you're designing it backwards. I would start from figuring out what kind of interface I want to present to my CPU first, for input devices it's usual to have a bunch of status registers, an interrupt line to the CPU, and a control register which allows you setting various options (for example enabling/disabling interrupts).Once you figure out that part, only then would I go about finding out how to implement a PHY (physical interface to the actual device).

author: nockieboy
author_rank: Super Contributor
timestamp: June 20, 2020, 05:08:29 pm
content: Quote from: asmi on June 20, 2020, 04:39:00 pmThe physical interface already exists - it gives the Z80 access to the GPU's video RAM. I thought it would only be another small step to allow the Z80 to have IO access to the FPGA, as well as accessing the memory (the only material difference is that a RD or WR from the Z80 is accompanied by MREQ going low for a memory operation, or IORQ going low for an IO operation).I have IO writes from the Z80 working, it's just the IO reads that I'm having trouble with currently. I suppose I could just place the keycode into the GPU RAM at a known location and the Z80 could just read it from RAM, but this could involve the added complexity of memory bank swapping, whereas an IO read would just be a single command and not care about the logical memory map.

author: BrianHG
author_rank: Super Contributor
timestamp: June 20, 2020, 05:18:32 pm
content: Quote from: asmi on June 20, 2020, 04:39:00 pmThis is the result of taking an off the shelf existing public domain keyboard PHY / decoder and tying it into your existing design instead of doing it all from scratch to your liking.Now, for a simple keyboard interface using the existing cores, I would probably choose to place a 16 word 'FIFO' (configured with overflow protection) between the existing interface and his Z80 bus. I would use the FIFO's empty flag on the output side to trigger a configurable interrupt. I would use the 4 bit 'used/filled' status output of the 'FIFO' at a second port address so the Z80 may see how many characters are in the keyboard buffer to be read. Sort of like the keyboard being read like a serial port.

author: nockieboy
author_rank: Super Contributor
timestamp: June 20, 2020, 05:29:32 pm
content: Quote from: BrianHG on June 20, 2020, 05:18:32 pmOkay, fair point re: the decoder. All these interface suggestions will be considered, I just thought I'd start small and try to understand the basics first. But right now I'm just trying to get my head around why I can't read a value (ANY value - even 0x00) from the PS2_CHAR register at an IO address on the z80_bridge? I can write to IO addresses fine on the z80_bridge.

author: Bassman59
author_rank: Super Contributor
timestamp: June 20, 2020, 07:31:37 pm
content: Quote from: nockieboy on June 19, 2020, 06:26:02 pmOnce upon a time, when you wrote an always block, you had to explicitly state all of the signals to which your block would be triggered. That's called a "sensitivity list." For simulation, the block would get triggered only when events occurred on the signals in that list.So you could model a three-input AND gate with a combinatorial always block like:Code:always @(in_a or in_b or in_c)beginq = in_a & in_b & in_c;end(and note the use of the blocking assignment.)The(in_a or in_b or in_c)is called the sensitivity list.The trouble begins when you omit a signal that appears on the right-hand-side of the assignment from your sensitivity list. If, for example, you omitin_cfrom the list, then the outputqwill never change whenin_cchanges, and that's not at all what you want.When you deal with a small combinatorial block like our AND gate, it's easy to keep track of the RHS signals. When you have a large combinatorial process, like a state decoder (for people who insist on using 2-block state machines), it's easy to omit something.The result of omitting a signal from the sensitivity list is a "simulation/synthesis mismatch." The simulator won't note the omission, because there's no way for it to know that you intended to omit a signal from the list. The synthesis won't care, it'll still build your three-input AND gate. This means the hardware won't match simulation and that's always bad.To get around the "problem" of signals missing from the sensitivity list, thealways @(*)notation was added to the language. This means "this block is sensitive to every signal on the right-hand-side of an assignment."Note that this shorthand is useful only for combinatorial blocks. When describing synchronous blocks (to infer flip-flops), the only signals that should be on the sensitivity list are the clock and if used an asynchronous reset. And specifically you must indicate that the block is sensitive to an edge of those signals, so the top of the block is always@(posedge clk or negedge rst_l). Why only the clock and reset? Think about how a real flip-flop works and you'll have your answer.

author: lintweaker
author_rank: Contributor
timestamp: June 21, 2020, 09:13:31 am
content: I have overcome similar issues doing I/O reads with a Z80 from devices in an FPGA (while also using level translators). I'd like to help but I am using VHDL and also very much a beginner.In VHDL my data return and bus isolaton looks something like (adjusted for your use case):Data return:Code:fData <= PS2Data when ( nPS2cs = '0' and fnRd = '0' ) else( others => 'Z');Bus isolationCode:fData_dir <= '1' when ( fnIorq = '0' or fnRd = '0' ) and ( nPS2cs = '0' ) else '0';PS2 controller select:Code:nPScs <= '0' when fAddress(7 downto 0) = "00000001" and ( fnIorq = '0' and fnRd = '0' ) else '1';-- Z80 I/O port 01h

author: nockieboy
author_rank: Super Contributor
timestamp: June 21, 2020, 05:58:11 pm
content: Quote from: lintweaker on June 21, 2020, 09:13:31 amI haven't had time today to look at the code, so it's probably going to be a slow-burn all week, but I'm growing more convinced it's an HDL code problem, so shouldn't be too hard to find unless it turns out to be a timing problem. Did you have any issues with timing when responding to an IO read by the Z80?

author: Bassman59
author_rank: Super Contributor
timestamp: June 21, 2020, 10:00:48 pm
content: Quote from: nockieboy on June 21, 2020, 05:58:11 pmHave you simulated the code?

author: nockieboy
author_rank: Super Contributor
timestamp: June 22, 2020, 10:51:07 am
content: Quote from: Bassman59 on June 21, 2020, 10:00:48 pmNo, I haven't - simulation seems like a bit of a dark art to me, but I guess no time like the present to finally grip it and start learning.

author: Bassman59
author_rank: Super Contributor
timestamp: June 22, 2020, 06:04:01 pm
content: Quote from: nockieboy on June 22, 2020, 10:51:07 amSimulation is very important to the design process. You can spend your life doing in-circuit debug to figure out why something doesn't work. Simulation allows you to get right into the design and see everything.Now here's the thing with simulation. It is only as good as your models. You need bus-functional models of all of the peripherals attached to your FPGA. Yes, this is a lot of work. Yes, it is all absolutely worth the effort.

author: nockieboy
author_rank: Super Contributor
timestamp: June 23, 2020, 02:53:41 pm
content: Well, colour me impressed.I can't say that the simulation was critical to finding the solution, but having a visual aid really helped me find the solution.So here's the initial simulation of what was going on with the z80_bridge module:The simulation isn't 100% accurate with timings, but as close as I could get it based off the Z80's datasheet for a 10 MHz chip (running at 8 MHz). With the z80_bridge running as it did above, I was getting a 6% error rate ('x's instead of expected values).Having looked at the output waveform, I wasn't overly happy that the data bus's 245 was enabled constantly - whatever was happening, it was driving data to or from the FPGA, so I made it so that it was disabled all the time it wasn't needed, only being enabled for RDs or WRs to the FPGA:This dropped the error rate to 1.6%. I thought I was making progress until I tried to read/write to the GPU RAM and found it was no longer working. With the intention of going back to the z80_bridge and trying to make the (previously working fine) memory ops work with the part-time 245, I thought I'd try one last thing - I noticed that z80_BDIR_EN and z80_DATA_DIR were swapping direction early in the IO cycle, perhaps I could tighten the timings on them too?So, with the z80_DATA bus now in hi-Z all the time it's not being used and direction of the 245 and bidirectional pins swapping to output literally only when required, I gave the software testing another try.... no errors at all.I'm still not quite surewhythe issue is fixed, but I'm now getting 100% error-free values back from the virtual PS/2 port in z80_bridge. So next I'll reconnect the PS/2 interface back up and see what happens next... Watch this space.

author: lintweaker
author_rank: Contributor
timestamp: June 24, 2020, 11:59:45 am
content: Quote from: nockieboy on June 23, 2020, 02:53:41 pmCongrats!That is exactly as I thoughtIn a lot of examples on the Internet I see returning of value 0xff instead of 'Z' .That did not work for when when using a real Z80.Setting the DIR pin of the level converters properly is crucial, without it you'll get odd results.Wrt timing issues: I see these only when the device internal in the FGPA is running at a different speed then the real Z80.

author: nockieboy
author_rank: Super Contributor
timestamp: June 24, 2020, 12:18:41 pm
content: Quote from: lintweaker on June 24, 2020, 11:59:45 amThanks.I'm now happy that the IO interface is working properly - I can read and write values to the FPGA with no errors at all now. I've now wired up the PS/2 socket and connected the keyboard and have connected up the PS/2 interface modules in the HDL as per my original post, but I'm still getting rubbish coming through when I type.Here's an example:Code:qAAAqAAAA@w@AA@AwAAAAAeAAAAeAAAAAr@@@@@r@@@@@t@@@@@@t@@@@@ssAAAAsAAAAAThat's the result of me typing 'qwerts' - looks like break codes are passing through when they shouldn't, and some random garbage is getting through as well - the A's and @'s. I have some alternative PS/2 interface modules to try as well, might give them a try.

author: asmi
author_rank: Super Contributor
timestamp: June 24, 2020, 01:12:17 pm
content: Quote from: nockieboy on June 24, 2020, 12:18:41 pmDon't do this. Instead find a spec for the interface and implement it properly as per spec. It's not complicated enough to warrant looking for third-party code and trying to get it to work.

author: nockieboy
author_rank: Super Contributor
timestamp: June 25, 2020, 01:20:47 pm
content: Quote from: asmi on June 24, 2020, 01:12:17 pmI appreciate the sentiment, but I don't see the point in re-inventing the wheel. I've actually got the interface working now usingsome code provided by Digikey. It's in VHDL, but was pretty much just drag-n-drop into the project. The only tweak I needed to make was to double the debounce timer to get a non-repeated output.I want to keep the keyboard interface as simple as possible, but one thing this PS/2 interface can't do is transmit data for simultaneous keys being pressed - it doesn't send break codes - so there's no way of knowing if multiple keys are being pressed. It's already as capable as the serial console that I've been using up to now, so this shouldn't be an issue (it handles SHIFT/CAPS/CTRL etc) but if I want to be able to read multiple key presses, like the up arrow and right arrow being pressed at the same time, I'll need to modify the code a little. I'd do this by merging the arrow keys into one byte before it is transmitted to the host - so the 'up' arrow could be b0, 'down' would be b1, etc. and just tweak the code on the host Z80 side so that if it receives a key press in a 16-byte range, then it just needs to decode that byte into the binary representation of which arrow keys are being held down. Does that sound about right?

author: BrianHG
author_rank: Super Contributor
timestamp: June 25, 2020, 04:57:21 pm
content: Wouldn't is work something more like a 'Key['#'] Down' and 'Key['#'] Up' ? So each key being pressed down transmits a activate signal. And only when the key is released would it send a de-activate for that key?This way, your Z80 will see any number of keys being pressed on and off simultaneously, or one after another, or held down for any amount of time?

author: nockieboy
author_rank: Super Contributor
timestamp: June 25, 2020, 06:12:40 pm
content: Quote from: BrianHG on June 25, 2020, 04:57:21 pmWell, that's the way PS/2 keyboards work, yes. The interface module I'm using dumbs that down a bit so it only sends 'key down' (or 'make') codes and doesn't send on the 'break' codes. It would involve some work on the host side to buffer 'make' codes and respond to the running program with a list of keys that are pressed at that instant, for little benefit currently. That's something I can work on later as and when the need arises.

author: BrianHG
author_rank: Super Contributor
timestamp: June 25, 2020, 10:10:29 pm
content: Quote from: nockieboy on June 25, 2020, 06:12:40 pmWell in the Z80, for normal keyboard operation, we are talking code as simple as when a 'make' is received, set that as a single strike and do nothing else, ignoring everything until ANY 'break' is received. Natural exception would be flags for combo keys like 'shift' and 'ctrl' and 'alt'. Only special apps like games would need direct access to 1 stage earlier. We are talking something like 8-12 assembly commands, not counting a lookup table if you need to translate to ascii.Or, in fact, maybe something like a lookup jump table, where whenever a 'make' is set, the jump would fill in the ascii or shift/ctrl/alf flags and a 'break' would clear the normal keys. This strategy may waste a good 64 to 128, or 256 additional bytes of program space, but, it would be stupid quick and difficult to mess up.Or, you can partially hold the flags and decode the basic ascii in verilog, and have 2 keyboard bytes to read. 1 for base bottom ascii code, the second byte for shift/ctrl/alt status and 'make' / 'break' being set, plus maybe an interrupt enable.