module tinydevice (input rawClk, input clk, input reset, // clock interface output clkOut, // CPU interface inout [7:0] dataBus, input [9:0] addrBus, // A9-A0 in input weMem, // memory interface output csRam, output oeRam, output weRam, output csRom, output oeRom, output weRom, output a18, output a17, output a16, output a15, output [5:0] addrBusHigh, // A14-A9 to memory // I/O interface input keyClk, // need pull-up input keyData, // need pull-up input navUp, input navDown, input navSet, input serialIn, // need pull-up inout [7:0] lcdData, output reg lcdCS1, output reg lcdCS2, output reg lcdRW, output reg lcdDI, output reg lcdE, // Read occurs during high E, write occurs at falling edge of E output reg ledRed, output reg ledGreen, output reg speaker, output serialOut); // -- 1K CPU Address Space Map // 000 start of block 0, boot address // 1FF top of block 0 // 200 start of block 1, start of scratch RAM // 3B7 top of scratch RAM (440 bytes) // 3B8 start of memory-mapped I/O // 3BF top of memory mapped I/O // 3C0 start of stack // 3FF top of stack, top of block 1 // -- 64K System Memory Organization // 1K CPU address space is partitioned into two 512-byte blocks // system memory is organized into 128 512-byte banks: 0-63 are ROM, 64-127 are RAM // block 0 can be mapped to any bank // block 1 is "common" and is always mapped to bank 127 wire ioRef = addrBus[9:3] == 7'b1110111; // I/O occupies 3B8-3BF wire commonBlock = addrBus[9]; reg [6:0] bankSel; // selects one of 128 banks to map to block 0 assign addrBusHigh = commonBlock ? 6'b111111 : bankSel[5:0]; // extra address bits are always 0 for now assign a18 = 1'b0; assign a17 = 1'b0; assign a16 = 1'b0; assign a15 = 1'b0; // ROM assign csRom = !commonBlock && !bankSel[6]; assign oeRom = !weMem; // should never write to ROM, but this prevents bus contention in case of a program bug assign weRom = 1'b1; // RAM: assign csRam = (commonBlock && !ioRef) || (!commonBlock && bankSel[6]); assign oeRam = !weMem; assign weRam = weMem; // we is only asserted from CPU during 2nd half of the clock cycle, no need to gate it again here // -- I/O `define aStatus 3'b000 // r/w, writing clears serInReady flag, active high [ x x serOutReady serInReady navSet navDown navUp keyDataReady ] `define aOutPort 3'b001 // r/w, active high [ x x x x x speaker ledGreen ledRed ] `define aLCDData 3'b010 // r/w (technically r/w, but LCD is too slow to support reads at CPU clock speed) `define aLCDCtrl 3'b011 // write-only /cs1, /cs2, /wr, d/i, e. Write occurs at falling edge of E `define aKeyData 3'b100 // r/w, writing clears keyDataReady flag `define aSerData 3'b101 // r/w `define aBankSel 3'b110 // r/w `define aTickCnt 3'b111 // read-only, increments every 3.2 us @ 5MHz, but is innacurate during serial output // -- Keyboard Interface reg [9:0] keyDataShift; reg keyClkLast; reg [3:0] keyBitCount; wire keyReset = !weMem && ioRef && addrBus[2:0] == `aKeyData; // set when writing to keyboard addr wire maxCount = keyBitCount == 4'b1011; wire keyDataReady = maxCount; // The keyboard transmits 11 bits: // 0 start // 1-8 data, LSB first // 9 odd parity // 10 stop // // My testing has shown there's always at least 3 ms between keyboard bytes, // even for the bytes in a multi-byte scan code for a single keypress. // The CPU must poll the keyboard faster than this, otherwise invalid reads // and synchronization problems will occur. always @(posedge clk or negedge reset) begin if (!reset) begin keyBitCount <= 0; keyClkLast <= 0; end else begin if (keyReset && maxCount) begin // only clear the bit count if it's maxed, to prevent framing errors keyBitCount <= 0; // clear the bit count and await a new keypress end else if (keyClkLast && !keyClk) begin // high to low transition? keyDataShift <= { keyData, keyDataShift[9:1] }; // shift data right, start bit will fall off if (maxCount) begin // if more bits arrive while there's still a byte waiting to be read, overwrite it keyBitCount <= 1'b1; end else begin keyBitCount <= keyBitCount + 1'b1; end end keyClkLast <= keyClk; end end // 57600 bps = 17.361111 us per bit, about 87 clocks @ 5MHz. // 38400 bps = 26.041666 us per bit, about 130 clocks @ 5MHz. `define cClocksPerBit 87 // -- Serial Output Interface for 8N1 wire serOutReady = serOutShift == 10'b0000000001; // indicates output buffer is ready for writing reg [6:0] serOutClockCount; reg [10:0] serOutShift; assign serialOut = serOutShift[0]; always @(posedge clk or negedge reset) begin if (!reset) begin serOutShift <= 11'b00000000001; serOutClockCount <= 0; end else if (ioRef && addrBus[2:0] == `aSerData && !weMem) begin serOutShift <= { 2'b11, dataBus, 1'b0 }; // load data for output serOutClockCount <= 0; end else if (!serOutReady && serOutClockCount == `cClocksPerBit) begin serOutShift <= { 1'b0, serOutShift[10:1] }; // right shift data out serOutClockCount <= 0; end else begin serOutClockCount <= serOutClockCount + 1'b1; end end // -- Serial Input Interface for 8N1 reg serInReady, serInReadyNext; // indicates incoming data is available for reading wire serInReadyClear = !weMem && ioRef && addrBus[2:0] == `aStatus; // when writing to status addr reg [6:0] serClockCount, serClockCountNext; reg [1:0] serState, serStateNext; reg [7:0] serInShift, serInShiftNext; reg [2:0] serBitCount, serBitCountNext; reg [7:0] serInBuf, serInBufNext; // serial input state machine `define sSerIdle 2'b00 `define sSerStart 2'b01 `define sSerData 2'b10 `define sSerStop 2'b11 always @(posedge clk or negedge reset) begin if (!reset) begin serClockCount <= 0; serState <= `sSerIdle; serBitCount <= 0; serInShift <= 0; serInReady <= 0; serInBuf <= 0; end else begin serClockCount <= serClockCountNext; serState <= serStateNext; serBitCount <= serBitCountNext; serInShift <= serInShiftNext; serInReady <= serInReadyNext; serInBuf <= serInBufNext; end end always @* begin serClockCountNext <= serClockCount + 1'b1; serStateNext <= serState; serBitCountNext <= serBitCount; serInShiftNext <= serInShift; serInReadyNext <= serInReadyClear ? 1'b0 : serInReady; serInBufNext <= serInBuf; case (serState) `sSerIdle: // serIn 0 marks beginning of start bit if (!serialIn) begin serStateNext <= `sSerStart; serClockCountNext <= 0; end `sSerStart: if (serClockCount == `cClocksPerBit / 2) begin serStateNext <= `sSerData; serClockCountNext <= 0; serBitCountNext <= 0; end `sSerData: if (serClockCount == `cClocksPerBit) begin serInShiftNext <= { serialIn, serInShift[7:1] }; // right shift data in serClockCountNext <= 0; if (serBitCount == 7) begin serStateNext <= `sSerStop; end else begin serBitCountNext <= serBitCount + 1'b1; end end `sSerStop: if (serClockCount == `cClocksPerBit) begin serStateNext <= `sSerIdle; serInBufNext <= serInShift; serInReadyNext <= 1; end endcase end // -- CPU Data Bus reg [7:0] dataOut; always @(posedge clk) begin case (addrBus[2:0]) default: // `aStatus dataOut <= { 2'b00, serOutReady, serInReady, !navSet, !navDown, !navUp, keyDataReady }; `aOutPort: dataOut <= { 5'b00000, speaker, ledGreen, ledRed }; `aLCDData: dataOut <= lcdData; `aLCDCtrl: dataOut <= { 3'b000, lcdCS1, lcdCS2, lcdRW, lcdDI, lcdE }; `aKeyData: dataOut <= keyDataShift[7:0]; `aSerData: dataOut <= serInBuf; `aBankSel: dataOut <= { 1'b0, bankSel }; `aTickCnt: dataOut <= tickCount; endcase end assign dataBus = (ioRef && weMem) ? dataOut : 8'bzzzzzzzz; // -- Out Port always @(posedge clk or negedge reset) begin if (!reset) begin speaker <= 0; ledGreen <= 1; ledRed <= 1; end else if (ioRef && addrBus[2:0] == `aOutPort && !weMem) begin { speaker, ledGreen, ledRed } <= dataBus[2:0]; end end // -- LCD Interface // Busy Check: // 1. Write LCDCtrl CS, Read, Instruction Reg // 2. Wait 140 ns // 3. Write LCDCtrl E // 4. Wait 500 ns (data valid after 320 ns, but min high pulse width for E is 500) // 5. Read LCDData[7] // 6. Write LCDCtrl /E // 7. Wait 500 ns (min low pulse width for E) // Read: // 1. Perform busy check // 2. Repeat busy check, but specifying Data Reg // 3. Reads are pipelined, must ready twice to get the value at address // Write: // 1. Perform busy check // 1. Write LCDCtrl CS, Write, Instruction/Data Reg // 2. Wait 140 ns // 3. Write LCDCtrl E // 4. Write LCDData // 5. Wait at least 200 ns from LCDData, or 500 ns from E // 6. Write LCDCtrl /E // 7. Wait 500 ns (min low pulse width for E) // -- LCD Data reg [7:0] lcdDataOut; assign lcdData = !lcdRW ? lcdDataOut : 8'bzzzzzzzz; always @(posedge clk or negedge reset) begin if (!reset) lcdDataOut <= 0; else if (ioRef && addrBus[2:0] == `aLCDData && !weMem) lcdDataOut <= dataBus; end // -- LCD Instruction always @(posedge clk or negedge reset) begin if (!reset) { lcdCS1, lcdCS2, lcdRW, lcdDI, lcdE } <= 5'b11100; else if (ioRef && addrBus[2:0] == `aLCDCtrl && !weMem) { lcdCS1, lcdCS2, lcdRW, lcdDI, lcdE } <= dataBus[4:0]; // add safeguard to prevent enabling both chips in read mode? end // -- Bank Select always @(posedge clk or negedge reset) begin if (!reset) bankSel <= 0; else if (ioRef && addrBus[2:0] == `aBankSel && !weMem) bankSel <= dataBus[6:0]; end // -- Tick count reg [7:0] tickCount; always @(posedge clk) begin if (serOutClockCount[3:0] == 0) // using serOutClockCount makes tickCount innaccurate during serial output tickCount <= tickCount + 1'b1; end // -- Clock synthesis // rawClk is divided down to generate clkOut, which is connected to clk externally reg [1:0] clkDiv; always @(posedge rawClk) begin clkDiv <= clkDiv + 1'b1; end assign clkOut = clkDiv[1]; // divide by 4 clock // -- Power-on state after JTAG reset (same as CPU reset state) initial begin keyBitCount <= 0; keyClkLast <= 0; bankSel <= 0; lcdDataOut <= 0; { lcdCS1, lcdCS2, lcdRW, lcdDI, lcdE } <= 5'b11100; speaker <= 0; ledGreen <= 1; ledRed <= 1; serInReady <= 0; serState <= `sSerIdle; serBitCount <= 0; serInShift <= 0; serInBuf <= 0; serOutShift <= 11'b00000000001; end endmodule