def __init__(self): self.submodules.fifo = f = fifo.SyncFIFOBuffered(width=8, depth=64) in_reg = CSRStorage(8, name="in", description=""" Write half of the FIFO to send data out the Messible. Writing to this register advances the write pointer automatically.""" ) out_reg = CSRStatus(8, name="out", description=""" Read half of the FIFO to receive data on the Messible. Reading from this register advances the read pointer automatically.""" ) self.__setattr__("in", in_reg) self.__setattr__("out", out_reg) self.status = status = CSRStatus(fields=[ CSRField( "full", description="``0`` if more data can fit into the IN FIFO."), CSRField( "have", description="``1`` if data can be read from the OUT FIFO."), ]) self.intro = ModuleDoc(""" Messible: An Ansible for Messages An Ansible is a system for instant communication across vast distances, from a small portable device to a huge terminal far away. A Messible is a message- passing system from embedded devices to a host system. You can use it to get very simple printf()-style support over a debug channel. The Messible assumes the host has a way to peek into the device's memory space. This is the case with the Wishbone bridge, which allows both the device and the host to access the same memory. At its core, a Messible is a FIFO. As long as the ``STATUS.FULL`` bit is ``0``, the device can write data into the Messible by writing into the ``IN``. However, if this value is ``1``, you need to decide if you want to wait for it to empty (if the other side is just slow), or if you want to drop the message. From the host side, you need to read ``STATUS.HAVE`` to see if there is data in the FIFO. If there is, read ``OUT`` to get the most recent byte, which automatically advances the ``READ`` pointer. """) self.comb += [ f.din.eq(in_reg.storage), f.we.eq(in_reg.re), out_reg.status.eq(f.dout), f.re.eq(out_reg.we), status.fields.full.eq(~f.writable), status.fields.have.eq(f.readable), ]
def __init__(self, pads, debounce_ms=5): self.background = ModuleDoc("""Matrix Keyboard Driver A hardware key scanner that can run even when the CPU is powered down or stopped. The hardware is assumed to be a matrix of switches, divided into rows and columns. The number of rows and columns is inferred by the number of pins dedicated in the `pads` record. The rows are inputs, and require a `PULLDOWN` to be inferred on the pins, to prevent them from floating when the keys are not pressed. Note that `PULLDOWN` is not offered on all FPGA architectures, but at the very least is present on 7-Series FPGAs. The columns are driven by tristate drivers. The state of the columns is either driven high (`1`), or hi-Z (`Z`). The KeyScan module also expects a `kbd` clock domain. The preferred implementation makes this a 32.768KHz always-on clock input with no PLL. This allows the keyboard module to continue scanning even if the CPU is powered down. Columns are scanned sequentially using a `kbd`-domain state machine by driving each column in order. When a column is driven, its electrical state goes from `hi-z` to `1`. The rows are then sampled with each column scan state. Since each row has a pulldown on it, if no keys are hit, the result is `0`. When a key is pressed, it will short a row to a column, and the `1` driven on a column will flip the row state to a `1`, thus registering a key press. Columns are driven for a minimum period of time known as a `settling` period. The settling time must be at least 2 because a Multireg (2-stage synchronizer) is used to sample the data on the receiving side. In this module, settling time is fixed to `4` cycles. Thus a 1 in the `rowdat` registers indicate the column intersection that was active for a given row in the matrix. There is also a row shadow register that is maintained by the hardware. The row shadow is used to maintain the previous state of the key matrix, so that a "key press change" interrupt can be generated. There is also a `keypressed` interrupt which fires every time there is a change in the row registers. `debounce` is the period in ms for the keyboard matrix to stabilize before triggering an interrupt. """) rows_unsync = pads.row cols = Signal(pads.col.nbits) self.uart_inject = Signal(8) # character to inject from the UART self.inject_strobe = Signal() # on rising edge, latch uart_inject and raise an interrupt self.uart_char = CSRStatus(9, fields = [ CSRField("char", size=8, description="character value being injected"), CSRField("stb", size=1, description="FIFO has readable characters"), ]) self.submodules.injectfifo = injectfifo = fifo.SyncFIFOBuffered(width=8, depth=16) self.comb += [ injectfifo.din.eq(self.uart_inject), injectfifo.we.eq(self.inject_strobe), #self.uart_char.fields.char.eq(self.uart_inject), self.uart_char.fields.char.eq(injectfifo.dout), self.uart_char.fields.stb.eq(injectfifo.readable), injectfifo.re.eq(self.uart_char.we), ] for c in range(0, cols.nbits): cols_ts = TSTriple(1) self.specials += cols_ts.get_tristate(pads.col[c]) self.comb += cols_ts.oe.eq(cols[c]) self.comb += cols_ts.o.eq(1) # row and col are n-bit signals that correspond to the row and columns of the keyboard matrix # each row will generate a column register with the column result in it rows = Signal(rows_unsync.nbits) self.specials += MultiReg(rows_unsync, rows, "kbd") # setattr(self, name, object) is the same as self.name = object, except in this case "name" can be dynamically generated # this is necessary here because we CSRStatus is not iterable, so we have to manage the attributes manually for r in range(0, rows.nbits): setattr(self, "row" + str(r) + "dat", CSRStatus(cols.nbits, name="row" + str(r) + "dat", description="""Column data for the given row""")) settling = 4 # 4 cycles to settle: 2 cycles for MultiReg stabilization + slop. Must be > 2, and a power of 2 colcount = Signal(max=(settling*cols.nbits+2)) update_shadow = Signal() reset_scan = Signal() scan_done = Signal() col_r = Signal(cols.nbits) scan_done_sys = Signal() self.specials += MultiReg(scan_done, scan_done_sys) for r in range(0, rows.nbits): row_scan = Signal(cols.nbits) row_scan_sys = Signal(cols.nbits) # below is in sysclock domain; row_scan is guaranteed stable by state machine sequencing when scan_done gating is enabled self.sync += [ row_scan_sys.eq(row_scan), # loosen up the clock domain crossing timing If(scan_done_sys, getattr(self, "row" + str(r) + "dat").status.eq(row_scan_sys) ).Else( getattr(self, "row" + str(r) + "dat").status.eq(getattr(self, "row" + str(r) + "dat").status) ) ] self.sync.kbd += [ If(reset_scan, row_scan.eq(0) ).Else( If(rows[r] & (colcount[0:2] == 3), # sample row only on the 4th cycle of colcount row_scan.eq(row_scan | col_r) ).Else( row_scan.eq(row_scan) ) ) ] rowshadow = Signal(cols.nbits) self.sync.kbd += If(update_shadow, rowshadow.eq(row_scan)).Else(rowshadow.eq(rowshadow)) setattr(self, "row_scan" + str(r), row_scan) setattr(self, "rowshadow" + str(r), rowshadow) # create a simple, one-scan delayed version of row_scan for debouncing purposes row_debounce = Signal(cols.nbits) self.sync.kbd += [ If(scan_done, row_debounce.eq(row_scan), ).Else( row_debounce.eq(row_debounce), ) ] setattr(self, "row_debounce" + str(r), row_debounce) pending_kbd = Signal() pending_kbd_f = Signal() key_ack = Signal() self.sync.kbd += [ pending_kbd_f.eq(pending_kbd), colcount.eq(colcount + 1), scan_done.eq(0), update_shadow.eq(0), reset_scan.eq(0), If(colcount == (settling*cols.nbits+2), colcount.eq(0)), If(colcount == (settling*cols.nbits), scan_done.eq(1)), # Only update the shadow if the pending bit has been cleared (e.g., CPU has acknowledged # it has fetched the current key state) If(~pending_kbd & pending_kbd_f, If(colcount == (settling * cols.nbits + 1), update_shadow.eq(1), key_ack.eq(0), ).Else( key_ack.eq(1), ) ).Else( If(colcount == (settling*cols.nbits+1), update_shadow.eq(key_ack), key_ack.eq(0), ).Else( key_ack.eq(key_ack) ) ), If(colcount == (settling*cols.nbits+2), reset_scan.eq(1)), ] # Drive the columns based on the colcount counter self.submodules.coldecoder = Decoder(cols.nbits) self.comb += [ self.coldecoder.i.eq(colcount[log2_int(settling):]), self.coldecoder.n.eq(~(colcount < settling*cols.nbits)), cols.eq(self.coldecoder.o) ] self.sync.kbd += col_r.eq(self.coldecoder.o) self.submodules.ev = EventManager() self.ev.keypressed = EventSourcePulse(description="Triggered every time there is a difference in the row state") # Rising edge triggered self.ev.inject = EventSourcePulse(description="Key injection request received") self.ev.finalize() self.sync += [ self.ev.inject.trigger.eq(self.inject_strobe) ] # zero state auto-clear: the "delta" methodology gets stuck if the initial sampling state of the keyboard matrix isn't 0. this fixes that. rows_nonzero = Signal(rows.nbits) col_zeros = Signal(cols.nbits) # form multi-bit zeros of the right width, otherwise we get 1'd0 as the rhs of equality statements row_zeros = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += rows_nonzero[r].eq(getattr(self, "row_scan" + str(r)) != col_zeros) all_zeros = Signal() clear_deltas = Signal() self.comb += all_zeros.eq(rows_nonzero == row_zeros) clear_kbd = Signal() self.submodules.clear_sync = BlindTransfer("sys", "kbd") self.comb += [ self.clear_sync.i.eq(self.ev.keypressed.clear), clear_kbd.eq(self.clear_sync.o) ] # debounce timers and clocks debounce_clocks = int((debounce_ms * 0.001) * 32768.0) debounce_timer = Signal(max=(debounce_clocks + 1)) debounced = Signal() # Extract any changes just before the shadow takes its new values rowdiff = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += [ If(clear_deltas, rowdiff[r].eq(0), ).Elif(debounced, # was scan_done rowdiff[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "rowshadow" + str(r))) == 0) ) ).Else( rowdiff[r].eq(rowdiff[r]) ) ] # debouncing: # 1. compute the delta of the current scan vs. previous scan # 2. if the delta is non-zero, start a debounce timer. # 3. Count down as long as the delta remains zero; if the delta is non-zero again, reset the timer. # 4. When the timer hits zero, latch the final value for sampling to the CPU rowchanging = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += [ If(clear_deltas, rowchanging[r].eq(0), ).Elif(scan_done, rowchanging[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "row_debounce" + str(r))) == 0) ) ).Else( rowchanging[r].eq(rowchanging[r]) ) ] db_fsm = ClockDomainsRenamer("kbd")(FSM(reset_state="IDLE")) self.submodules += db_fsm db_fsm.act("IDLE", If(rowchanging != 0, NextValue(debounce_timer, debounce_clocks), NextState("DEBOUNCING") ) ) db_fsm.act("DEBOUNCING", If(rowchanging == 0, NextValue(debounce_timer, debounce_timer - 1), If(debounce_timer == 0, NextState("DEBOUNCED") ) ).Else( NextValue(debounce_timer, debounce_clocks), NextState("DEBOUNCING") ) ) db_fsm.act("DEBOUNCED", If(scan_done, debounced.eq(1), NextState("IDLE") ) ) self.comb += clear_deltas.eq(all_zeros & clear_kbd & db_fsm.ongoing("IDLE")) # Fire an interrupt during the reset_scan phase. changed = Signal() changed_sys = Signal() kp_r = Signal() changed_kbd = Signal() debounced_d = Signal() self.sync.kbd += debounced_d.eq(debounced) self.kbd_wakeup = Signal() self.comb += changed.eq(debounced_d & (rowdiff != 0)) self.sync.kbd += [ If(changed, changed_kbd.eq(1) ).Elif(key_ack, changed_kbd.eq(0) ).Else( changed_kbd.eq(changed_kbd) ) ] self.comb += self.kbd_wakeup.eq(changed_kbd | changed) self.specials += MultiReg(changed_kbd, changed_sys) self.sync += kp_r.eq(changed_sys) self.comb += self.ev.keypressed.trigger.eq(changed_sys & ~kp_r) self.submodules.pending_sync = BlindTransfer("sys", "kbd") # pulse-convert pending into a single pulse on the falling edge -- otherwise we get a pulse train kp_pending_sys = Signal() kp_pending_sys_r = Signal() self.sync += [ kp_pending_sys.eq(self.ev.keypressed.pending), kp_pending_sys_r.eq(kp_pending_sys), ] self.comb += [ self.pending_sync.i.eq(~kp_pending_sys & kp_pending_sys_r), pending_kbd.eq(self.pending_sync.o), ]
def __init__(self, platform, mem, minimal=False): self.intro = ModuleDoc("""FOMU Apple II+ A virtual computer within a virtual computer inside a USB port. Instantiate 6502 processor with 1MHz core clock. Tie in to system memory as a wishbone master. Create virtual keyboard, video display and disk drive. """) addr = Signal(16) dout = Signal(8) din = Signal(8) wren = Signal() iosel = Signal() ior_addr = Signal(8) r_memsel = Signal() w_memsel = Signal() clk_en = Signal() # Clock divider limits CPU activity active = Signal() # CPU is active this cycle available = Signal() # Key press ready for reading div1M = 4 idlecount = Signal(div1M) # Counter to slow CPU clock # Disk II Controller Registers disk_phase = Signal(4) disk_motor = Signal() disk_drive = Signal() #disk_write = Signal() disk_reading = Signal() # DOS trying to read sector disk_data_available = Signal() # Data available for DOS to read disk_data_wanted = Signal() # Data wanted by DOS disk_read = Signal() # Data read by DOS so clear readable simulation = getenv("SIMULATION") synthesis = not simulation # Wishbone visible registers self.control = CSRStorage(fields=[ CSRField( "Reset", reset=1 if synthesis else 0, # auto-start in sim description="6502 Reset line - 1: Reset Asserted, 0: Running"), CSRField("RWROM", size=1, description="Allow writes to ROM"), #CSRField("Pause", description="Halt processor allowing stepping"), #CSRField("Step", description="Single step 6502 one clock cycle"), #CSRField("NMI", size=1, description="Non-maskable interrupt"), #CSRField("IRQ", size=1, description="Maskable interrupt request"), #CSRField("RDY", size=1, description=""), CSRField( "Divisor", size=div1M, offset=8, reset=11 if synthesis else 0, # Over-clock simulation description="Clock divider minius 1: 11 for 1MHz, 0 for 12MHz" ), #CSRField("DI", size=8, offset=16, description=""), ]) self.keyboard = CSRStorage(8, write_from_dev=True, description="Keyboard input ($C000)") self.strobe = CSR(1) #, description="Keyboard strobe ($C010)") self.screen = CSRStatus( fields=[ CSRField("Character", size=8, description="Character written to screen"), CSRField("Valid", size=1, description="Character is valid"), CSRField( "More", size=1, description="Additional characters are available to read"), #CSRField("Move", size=1, # description="Character is not adjacent to previous"), CSRField("Repeat", size=1, offset=11, description= "Previous character repeated to current position"), CSRField("ScrollStart", size=1, offset=12, description="Start of scroll region detected"), CSRField("ScrollEnd", size=1, offset=13, description="End of scroll region"), CSRField( "Horizontal", size=6, offset=16, description="Location of current character in screen memory" ), CSRField( "Vertical", size=5, offset=24, description="Location of current character in screen memory" ), ], description="Video Display Output") self.diskctrl = CSRStatus( fields=[ CSRField("Phase", size=4, description= "Four phases of the track selection stepper motor"), CSRField("Motor", size=1, description="Drive is spinning"), CSRField( "Drive", size=1, description="Drive select: drive 1 if clear, drive 2 if set" ), CSRField("Wanted", size=1, description="Drive is waiting for data"), CSRField("Pending", size=1, description="Drive has not yet read data written"), #CSRField("WriteMode", size=1, # description="Drive is reading when clear, writing when set"), ], description="Disk drive control ($C0EX)") self.diskdata = CSRStorage(8, description="Disk drive data ($C0EC)") #self.bus=CSRStatus(32, fields=[ # CSRField("Addr", size=16, description="Address bus"), # CSRField("Data", size=8, description="Data bus"), # CSRField("WrEn", size=1, description="Write enable"), # ], description="Address and data bus") #self.debug=CSRStatus(32, fields=[ # CSRField("PC", size=8, # description="Program counter"), # CSRField("A", size=8, # description="Accumulator"), # CSRField("X", size=8, # description="X index register"), # CSRField("Y", size=8, # description="Y index register"), # ], description="Address and data bus") if not minimal: # The minimal configuration provides all registers the host needs to # see so software can run unmodified. However, it does not implement # the 6502 to save gates. The video driver is also greatly reduced. # TODO eliminate wire [31:0] apple2_display_fifo_wrport_dat_r self.submodules.display_fifo = fifo.SyncFIFOBuffered(width=32, depth=256) self.comb += [ mem.addr6502.eq(addr), mem.din6502.eq(dout), mem.wren6502.eq(wren), self.strobe.w.eq(available), #self.bus.fields.Addr.eq(addr), #self.bus.fields.Data.eq(dout), #self.bus.fields.WrEn.eq(wren), If(addr[8:16] == 0xC0, iosel.eq(1), w_memsel.eq(0), mem.access6502.eq(0)).Else(iosel.eq(0), w_memsel.eq(1), mem.access6502.eq(active)), disk_read.eq(0), If( r_memsel, din.eq(mem.dout6502), ).Else( # I/O Read Address Decoder (reading but not from memory) If( ior_addr[4:8] == 0x0, din.eq(Cat(self.keyboard.storage[0:7], available)), ), # Disk II Controller Card in slot 6 (0x8 | 0x6) # The only data to be read are locations C and D. Simplify the # logic to only look at bit 0 of the address. If( ior_addr[4:8] == 0xE, din[0:7].eq(self.diskdata.storage[0:7]), # Write protect - TODO write is not supported #If(ior_addr[0], # # Return high bit set - protected # din[7].eq(1) #).Else( disk_read.eq(1), If( disk_data_available | self.diskdata.re, # Return byte given by host din[7].eq(self.diskdata.storage[7]), ).Else( # Return high bit clear - data not available din[7].eq(0), ), #), ), ), active.eq(clk_en & self.display_fifo.writable), ] self.sync += [ # Slow clock to prototypical speed or as configured by user If( idlecount == self.control.fields.Divisor, idlecount.eq(0), clk_en.eq(1), ).Else( idlecount.eq(idlecount + 1), clk_en.eq(0), ), # Read (DI) follows Write (AB/DO) by one clock cycle If( active, r_memsel.eq(w_memsel), ior_addr.eq(addr[0:8]), ), # Keyboard key available when written by host If( self.keyboard.re, available.eq(1), ), If( iosel, # I/O Write Address Decoder If( addr[4:8] == 0x1, # KBDSTRB # Strobe cleared on read or write to KBDSTRB # Any read or write to this address clears the pending key available.eq(0), ), ), ] self.specials += Instance( "cpu", i_clk=ClockSignal(), i_reset=ResetSignal() | self.control.storage[0], i_DI=din, # &(self.rand.we|self.cfg.storage[1])), o_AB=addr, # .dat_w, o_DO=dout, # .dat_w, o_WE=wren, i_IRQ=False, # self.control.fields.IRQ, i_NMI=False, # self.control.fields.NMI, # seed.re, i_RDY=active, # self.control.fields.RDY, ) platform.add_source("rtl/verilog-6502/cpu.v") platform.add_source("rtl/verilog-6502/ALU.v") #=============================================================================== # Disk Drive - Emulate Disk II controller in slot 6 #=============================================================================== # The Disk II controller card has 16 addressable locations. Eight of these are # dedicated to moving the arm, two for motor control, two for drive selection # and four that handle read, write, and write protection detection. #=============================================================================== self.comb += [ self.diskctrl.fields.Phase.eq(disk_phase), self.diskctrl.fields.Motor.eq(disk_motor), self.diskctrl.fields.Drive.eq(disk_drive), #self.diskctrl.fields.WriteMode.eq(disk_write), self.diskctrl.fields.Wanted.eq(disk_data_wanted), self.diskctrl.fields.Pending.eq(disk_data_available), ] self.sync += [ If( self.diskdata.re, disk_data_available.eq(1), ), # Set false again on read If( active & disk_read, disk_reading.eq(0), If( disk_data_available, disk_data_wanted.eq(0), ).Else(disk_data_wanted.eq(1), ), disk_data_available.eq(0), ), If( iosel, # Disk II Controller Card in slot 6 # C0E0 PHASEOFF Stepper motor phase 0 off. # C0E1 PHASEON Stepper motor phase 0 on. # C0E2 PHASE1OFF Stepper motor phase 1 off. # C0E3 PHASElON Stepper motor phase 1 on. # C0E4 PHASE2OFF Stepper motor phase 2 off. # C0E5 PHASE2ON Stepper notor phase 2 on. # C0E6 PHASE3OFF Stepper motor phase 3 off. # C0E7 PHASE3ON Stepper motor phase 3 on. # C0E8 MOTOROFF Turn motor off. # C0E9 MOTORON Turn motor on. # C0EA DRV0EN Engage drive 1. # C0EB DRV1EN Engage drive 2. # C0EC Q6L Strobe Data Latch for I/O. # C0ED Q6H Load Data Latch. # C0EE Q7H Prepare latch for input (read from disk). # C0EF Q7L Prepare latch for output (write to disk). # Q7L with Q6L = Read # Q7L with Q6H = Sense Write Protect # Q7H with Q6L = Write # Q7H with Q6H = Load Write Latch If( addr[4:8] == 0xE, # (8|6) # Addresses 0-7 simply update a bit in the status register If(addr[0:4] == 0x0, disk_phase[0].eq(0)), If(addr[0:4] == 0x1, disk_phase[0].eq(1)), If(addr[0:4] == 0x2, disk_phase[1].eq(0)), If(addr[0:4] == 0x3, disk_phase[1].eq(1)), If(addr[0:4] == 0x4, disk_phase[2].eq(0)), If(addr[0:4] == 0x5, disk_phase[2].eq(1)), If(addr[0:4] == 0x6, disk_phase[3].eq(0)), If(addr[0:4] == 0x7, disk_phase[3].eq(1)), # Likewise, motor active and drive select update status If(addr[0:4] == 0x8, disk_motor.eq(0)), If(addr[0:4] == 0x9, disk_motor.eq(1)), If(addr[0:4] == 0xA, disk_drive.eq(0)), If(addr[0:4] == 0xB, disk_drive.eq(1)), # Write is ignored and read must be delayed one clock tick If(addr[0:4] == 0xC, disk_reading.eq(1)), #If(addr[0:4]==0xD, disk_ior_wp.eq(1)), #If(addr[0:4]==0xE, disk_write.eq(0)), #If(addr[0:4]==0xF, disk_write.eq(1)), ), ), ] #=============================================================================== # Video Output - Text Mode #=============================================================================== # The Apple II screen memory contains 8 segments containing 3 rows each in a 128 # byte block leaving 8 unused bytes in each of the 8 blocks To assist with # scroll detection, we convert memory addresses to screen coordinates. #=============================================================================== # Video memory - Frame Buffer access shortcuts fbsel = Signal() fb_r = Signal() fb_w = Signal() # Conversion from memory address to X,Y screen coordinates segment = Signal(3) triple = Signal(7) third = Signal(2) horiz = Signal(6) vert = Signal(5) move = Signal() scroll_active = Signal() scroll_match = Signal() scroll_start = Signal() scroll_end = Signal() scroll_read = Signal() scroll_write_valid = Signal() scroll_next_col = Signal() scroll_next_row = Signal() scroll_sequential = Signal() read_horiz = Signal(6) read_vert = Signal(5) repeat_active = Signal() repeat_match = Signal() repeat_start = Signal() repeat_end = Signal() repeat_next_col = Signal() repeat_next_row = Signal() repeat_sequential = Signal() # Registers shared by scroll and repeat compression circuits #horiz_start = Signal(max=40) #vert_start = Signal(max=24) #horiz_end = Signal(max=40) #vert_end = Signal(max=24) prev_horiz = Signal(max=40) prev_vert = Signal(max=24) prev_char = Signal(8) prev_start = Signal() push_save = Signal() push_saving = Signal() fifo_out = Signal(32) self.comb += [ # Detect access to frame memory: Address range 0x0400-0x7ff fbsel.eq((addr[10:15] == 0x1) & active), fb_r.eq(fbsel & ~wren), fb_w.eq(fbsel & wren), # Convert memory address to X,Y coordinates segment.eq(addr[7:10]), triple.eq(addr[0:7]), # TODO This generates reg - change to cause only wire If( triple >= 80, third.eq(2), horiz.eq(addr[0:7] - 80), ).Else( If( triple >= 40, third.eq(1), horiz.eq(addr[0:7] - 40), ).Else( third.eq(0), horiz.eq(addr[0:7]), )), vert.eq(Cat(segment, third)), # TODO Detect scroll - frame buffer read immediately followed by # frame buffer write to character on previous line, directly above. # Scroll is Right to Left (asm: DEY at FC90 in Autostart ROM) scroll_match.eq((horiz == read_horiz) & (vert + 1 == read_vert)), # & (din==read_char)) <== TODO Need to delay din by 1 cycle scroll_write_valid.eq(scroll_read & fb_w & scroll_match), scroll_start.eq(scroll_write_valid & ~scroll_active), # Scroll ends on any write that does not follow the required pattern scroll_end.eq(scroll_active & fb_w & ~scroll_write_valid), scroll_next_col.eq((horiz + 1 == prev_horiz) & (vert == prev_vert)), scroll_next_row.eq((horiz == 39) & (prev_horiz == 0) & (vert == prev_vert + 1)), scroll_sequential.eq(scroll_next_col | scroll_next_row), # Detect repeated charaters (spaces) # Clear is Left to Right (asm: INY at FCA2 in Autostart ROM) # repeat_match.eq(fb_w & (dout==prev_char)), # repeat_start.eq(repeat_match & repeat_sequential & ~repeat_active), # repeat_end.eq(fb_w & repeat_active & # (~repeat_match |~repeat_sequential)), # repeat_next_col.eq((horiz==prev_horiz+1) & (vert==prev_vert)), # repeat_next_row.eq((horiz==0) & (prev_horiz==39) & # (vert==prev_vert+1)), # repeat_sequential.eq(repeat_next_col | repeat_next_row), # repeat_sequential.eq(repeat_next_col), # This or the previous one # Place writes in the fifo self.display_fifo.din[8].eq(0), # Valid is calculated self.display_fifo.din[9].eq(0), # More is calculated #self.display_fifo.din[ 10].eq(move), self.display_fifo.din[11].eq(repeat_end), If( push_save, self.display_fifo.din[0:8].eq(prev_char), self.display_fifo.din[12].eq(prev_start), self.display_fifo.din[13].eq(scroll_end), #self.display_fifo.din[14:16].eq(0), # Reserved self.display_fifo.din[16:22].eq(prev_horiz ), # 2 bits padding self.display_fifo.din[24:29].eq( prev_vert), # 3 bits padding ).Elif( push_saving, # push_save will be valid on the next cycle - so push previous self.display_fifo.din[0:8].eq(prev_char), #self.display_fifo.din[ 8].eq(0), # Valid is calculated #self.display_fifo.din[ 9].eq(0), # More is calculated #self.display_fifo.din[ 10].eq(move), #self.display_fifo.din[ 11].eq(repeat_end), self.display_fifo.din[12].eq(scroll_start), self.display_fifo.din[13].eq(scroll_end), #self.display_fifo.din[14:16].eq(0), # Reserved self.display_fifo.din[16:22].eq(prev_horiz ), # 2 bits padding self.display_fifo.din[24:29].eq( prev_vert), # 3 bits padding ).Else( #self.display_fifo.din.eq(Cat(dout, horiz, vert, # move, scroll_start, scroll_end, repeat_start, repeat_end)), self.display_fifo.din[0:8].eq(dout), #self.display_fifo.din[ 8].eq(0), # Valid is calculated #self.display_fifo.din[ 9].eq(0), # More is calculated #self.display_fifo.din[ 10].eq(move), #self.display_fifo.din[ 11].eq(repeat_end), self.display_fifo.din[12].eq(scroll_start), self.display_fifo.din[13].eq(scroll_end), #self.display_fifo.din[14:16].eq(0), # Reserved self.display_fifo.din[16:22].eq(horiz), # 2 bits padding self.display_fifo.din[24:29].eq(vert), # 3 bits padding ), self.display_fifo.we.eq(push_save | repeat_end | scroll_start | scroll_end | (fb_w & ~scroll_active & ~repeat_active & ~repeat_start)), push_saving.eq(((repeat_end & ~repeat_sequential) | scroll_end) & ~push_save), # Retrieve characters from fifo self.display_fifo.re.eq(self.screen.we), self.screen.we.eq(self.display_fifo.re), self.screen.fields.Valid.eq(self.display_fifo.readable), self.screen.fields.More.eq(self.display_fifo.readable), self.screen.fields.Character.eq(fifo_out[0:8]), self.screen.fields.Horizontal.eq(fifo_out[16:22]), self.screen.fields.Vertical.eq(fifo_out[24:29]), #self.screen.fields.Move.eq(fifo_out[10]), self.screen.fields.Repeat.eq(fifo_out[11]), self.screen.fields.ScrollStart.eq(fifo_out[12]), self.screen.fields.ScrollEnd.eq(fifo_out[13]), ] self.sync += [ fifo_out.eq(self.display_fifo.dout), # Scroll If( scroll_start, scroll_active.eq(1), #horiz_start.eq(horiz), #vert_start.eq(vert), #horiz_end.eq(horiz), #vert_end.eq(vert), ), If( scroll_end, push_save.eq(1), scroll_active.eq(0), # These happen on any write to the frame buffer #prev_horiz.eq(horiz), #prev_vert.eq(vert), #prev_char.eq(dout), prev_start.eq(scroll_start), ), If( fb_r, If((scroll_read & (scroll_sequential | ~scroll_active)) | (repeat_active & repeat_sequential), # A faithful 6502 model issues a read of the target # address before the write cycle of an indirect store # instruction. Do nothing if we suspect the current read is # actually part of a store instruction. ). Else( scroll_read.eq(1), read_horiz.eq(horiz), read_vert.eq(vert), # TODO read_char should be din on the next clock cycle # read_char.eq(dout), ), ), # Write to the frame buffer: remember the location and character # that generate the sequential and repeat signals which are used # by the scroll and clear functions. Also, update scroll signals. If( fb_w, scroll_read.eq(0), prev_vert.eq(vert), prev_horiz.eq(horiz), prev_char.eq(dout), # Repeat - Mostly needed for screen clearing operations but made # generic to conserve bandwidth and allow any character to be # repeated. If( repeat_match & repeat_sequential, # Supress output, begin sequence if not started already # Store location and character as this will be needed if the # following write is not sequential # Setting repeat is redundant if already active repeat_active.eq(1), ).Elif( repeat_active & ~repeat_sequential, # The cursor moved and we must terminate repeat mode # indicating the last character in the sequence. This is # required whether or not the same character is present. # Output saved location with Repeat flag set to mark end. # Then output current signal set on next cycle push_save.eq(1), prev_start.eq(scroll_start), repeat_active.eq(0), ).Else( # Push current character on stack either because: # a. character is different breaking repeat # b. character is different preventing repeat # c. location is not sequential breaking repeat # d. location is not sequential preventing repeat # Cases a,c need to clear repeat. This is irrelevant for b,d repeat_active.eq(0), ), ), # We can safely use the cycle after a write to frame memory as a # second push into the character fifo knowing that the only time the # 6502 has two consecutive write cycles is the JSR instruction which # writes the return address onto the stack, and the RMW instructions # INC, DEC, LSR, ASR, ROL, ROR which write the original and the new # values in two consecutive cycles. It is extremely poor programming # practice to keep the stack inside the frame buffer while clearing # or scrolling the screen so no special handling of these cases is # taken. If( push_save, # Auto-clear after one clock cycle push_save.eq(0), ), ]
def __init__(self, usb_core): self.submodules.data_buf = buf = ResetInserter()(fifo.SyncFIFOBuffered( width=8, depth=66)) self.data = data = CSRStatus(fields=[ CSRField("data", 8, description="The top byte of the receive FIFO."), ], description=""" Data received from the host will go into a FIFO. This register reflects the contents of the top byte in that FIFO. Reading from this register advances the FIFO pointer.""") self.ctrl = ctrl = CSRStorage(fields=[ CSRField( "epno", 4, description= "The endpoint number to update the ``enable`` and ``status`` bits for." ), CSRField( "enable", description="Write a ``1`` here to enable receiving data"), CSRField( "reset", pulse=True, description="Write a ``1`` here to reset the ``OUT`` handler"), CSRField("stall", description="Write a ``1`` here to stall an endpoint"), ], description=""" Controls for receiving packet data. To enable an endpoint, write its value to ``epno``, with the ``enable`` bit set to ``1`` to enable an endpoint, or ``0`` to disable it. Resetting the OutHandler will set all ``enable`` bits to 0. Similarly, you can adjust the ``STALL`` state by setting or clearing the ``stall`` bit.""" ) self.status = CSRStatus( fields=[ CSRField( "epno", 4, description= "The destination endpoint for the most recent ``OUT`` packet." ), CSRField("have", description="``1`` if there is data in the FIFO."), CSRField("pend", description="``1`` if there is an IRQ pending."), ], description="Status about the current state of the `OUT` endpoint." ) self.submodules.ev = ev.EventManager() self.ev.submodules.packet = ev.EventSourcePulse(name="done", description=""" Indicates that an ``OUT`` packet has successfully been transferred from the host. This bit must be cleared in order to receive additional packets.""") self.ev.finalize() self.usb_reset = Signal() self.stalled = Signal() self.enabled = Signal() stall_status = Signal(16) enable_status = Signal(16) ep_mask = Signal(16, reset=1) self.comb += [ If( usb_core.setup | usb_core.commit, ep_mask.eq(1 << usb_core.endp), ).Else(ep_mask.eq(1 << ctrl.fields.epno), ), self.stalled.eq(stall_status >> usb_core.endp), self.enabled.eq(enable_status >> usb_core.endp), ] self.sync += [ If( ctrl.fields.reset | self.usb_reset, stall_status.eq(0), ).Elif( usb_core.setup | (ctrl.re & ~ctrl.fields.stall), # If a SETUP packet comes in, clear the STALL bit. stall_status.eq(stall_status & ~ep_mask), ).Elif( ctrl.re, stall_status.eq(stall_status | ep_mask), ), ] # The endpoint number of the most recently received packet epno = Signal(4) # How to respond to requests: # - 1 - ACK # - 0 - NAK # Send a NAK if the buffer contains data, or if "ENABLE" has not been set. self.response = Signal() responding = Signal() is_out_packet = Signal() # Keep track of whether we're currently responding. self.comb += is_out_packet.eq(usb_core.tok == PID.OUT) self.comb += self.response.eq(self.enabled & is_out_packet & ~self.ev.packet.pending) self.sync += If(usb_core.poll, responding.eq(self.response)) # Connect the buffer to the USB system self.data_recv_payload = Signal(8) self.data_recv_put = Signal() self.comb += [ buf.din.eq(self.data_recv_payload), buf.we.eq(self.data_recv_put & responding), buf.reset.eq(ctrl.fields.reset), self.data.fields.data.eq(buf.dout), # When data is read, advance the FIFO buf.re.eq(data.we), self.status.fields.epno.eq(epno), self.status.fields.have.eq(buf.readable), self.status.fields.pend.eq(self.ev.packet.pending), # When data is successfully transferred, the buffer becomes full. # This is true even if "no" data was transferred, because the # buffer will then contain two bytes of CRC16 data. # Therefore, if the FIFO is readable, an interrupt must be triggered. self.ev.packet.trigger.eq(responding & usb_core.commit), ] # If we get a packet, turn off the "IDLE" flag and keep it off until the packet has finished. self.sync += [ If( ctrl.fields.reset, enable_status.eq(0), ).Elif( usb_core.commit & responding, epno.eq(usb_core.endp), # Disable this EP when a transfer finishes enable_status.eq(enable_status & ~ep_mask), responding.eq(0), ).Elif( ctrl.re, # Enable or disable the EP as necessary If( ctrl.fields.enable, enable_status.eq(enable_status | ep_mask), ).Else(enable_status.eq(enable_status & ~ep_mask), ), ), ]
def __init__(self, usb_core): self.dtb = Signal() # Keep track of the current DTB for each of the 16 endpoints dtbs = Signal(16, reset=0x0001) # A list of endpoints that are stalled stall_status = Signal(16) self.submodules.data_buf = buf = ResetInserter()(fifo.SyncFIFOBuffered( width=8, depth=64)) self.data = CSRStorage(fields=[ CSRField("data", 8, description="The next byte to add to the queue."), ], description=""" Each byte written into this register gets added to an outgoing FIFO. Any bytes that are written here will be transmitted in the order in which they were added. The FIFO queue is automatically advanced with each write. The FIFO queue is 64 bytes deep. If you exceed this amount, the result is undefined.""" ) self.ctrl = ctrl = CSRStorage(fields=[ CSRField( "epno", 4, description= "The endpoint number for the transaction that is queued in the FIFO." ), CSRField("reset", offset=5, description= "Write a ``1`` here to clear the contents of the FIFO.", pulse=True), CSRField("stall", description= "Write a ``1`` here to stall the EP written in ``EP``.", pulse=True), ], description=""" Enables transmission of data in response to ``IN`` tokens, or resets the contents of the FIFO.""") self.status = CSRStatus(fields=[ CSRField( "idle", description= "This value is ``1`` if the packet has finished transmitting." ), CSRField("have", offset=4, description="This value is ``0`` if the FIFO is empty."), CSRField("pend", offset=5, description="``1`` if there is an IRQ pending."), ], description=""" Status about the IN handler. As soon as you write to `IN_DATA`, ``IN_STATUS.HAVE`` should go to ``1``.""") self.submodules.ev = ev.EventManager() self.ev.submodules.packet = ev.EventSourcePulse(name="done", description=""" Indicates that the host has successfully transferred an ``IN`` packet, and that the FIFO is now empty. """) self.ev.finalize() # Control bits ep_stall_mask = Signal(16) self.comb += [ ep_stall_mask.eq(1 << ctrl.fields.epno), ] # Keep track of which endpoints are currently stalled self.stalled = Signal() self.comb += self.stalled.eq(stall_status >> usb_core.endp) self.sync += [ If( ctrl.fields.reset, stall_status.eq(0), ).Elif( usb_core.setup | (ctrl.re & ~ctrl.fields.stall), # If a SETUP packet comes in, clear the STALL bit. stall_status.eq(stall_status & ~ep_stall_mask), ).Elif( ctrl.re, stall_status.eq(stall_status | ep_stall_mask), ), ] # How to respond to requests: # - 0 - ACK # - 1 - NAK self.response = Signal() # This value goes "1" when data is pending, and returns to "0" when it's done. queued = Signal() was_queued = Signal() # This goes to "1" when "queued" is 1 when a "start" occurs. It is used # to avoid skipping packets when a packet is queued during a transmission. transmitted = Signal() self.dtb_reset = Signal() self.comb += [ buf.reset.eq(ctrl.fields.reset | (usb_core.commit & transmitted & queued)), ] # Outgoing data will be placed on this signal self.data_out = Signal(8) # This is "1" if `data_out` contains data self.data_out_have = Signal() # Pulse this to advance the data output self.data_out_advance = Signal() # Used to detect when an IN packet finished is_our_packet = Signal() is_in_packet = Signal() self.comb += [ # We will respond with "ACK" if the register matches the current endpoint number self.response.eq(queued & is_our_packet & is_in_packet), # Wire up the "status" register self.status.fields.have.eq(buf.readable), self.status.fields.idle.eq(~queued), self.status.fields.pend.eq(self.ev.packet.pending), # Cause a trigger event when the `queued` value goes to 0 self.ev.packet.trigger.eq(~queued & was_queued), self.dtb.eq(dtbs >> usb_core.endp), self.data_out.eq(buf.dout), self.data_out_have.eq(buf.readable), buf.re.eq(self.data_out_advance & is_in_packet & is_our_packet), buf.we.eq(self.data.re), buf.din.eq(self.data.storage), is_our_packet.eq(usb_core.endp == ctrl.fields.epno), is_in_packet.eq(usb_core.tok == PID.IN), ] self.sync += [ If( ctrl.fields.reset, queued.eq(0), was_queued.eq(0), transmitted.eq(0), dtbs.eq(0x0001), ).Elif( self.dtb_reset, dtbs.eq(dtbs | 1), ) # When the user updates the `ctrl` register, enable writing. .Elif( ctrl.re & ~ctrl.fields.stall, queued.eq(1), ).Elif( usb_core.poll & self.response, transmitted.eq(1), ) # When the USB core finishes operating on this packet, # de-assert the queue flag .Elif( usb_core.commit & transmitted & self.response & ~self.stalled, queued.eq(0), transmitted.eq(0), # Toggle the "DTB" line if we transmitted data dtbs.eq(dtbs ^ (1 << ctrl.fields.epno)), ).Else(was_queued.eq(queued), ), ]
def __init__(self): self.submodules.data = buf = fifo.SyncFIFOBuffered(width=8, depth=10) # Indicates which byte of `SETUP` data we're currently on. data_byte = Signal(4) # If the incoming `SETUP` token indicates there will be # a DATA stage, this will be set to 1. self.have_data_stage = have_data_stage = Signal() # If the incoming `SETUP` token is an OUT packet, this # will be 1. self.is_in = is_in = Signal() self.empty = Signal() self.comb += self.empty.eq(~buf.readable) # Wire up the `STATUS` register self.comb += [ status.fields.have.eq(buf.readable), status.fields.is_in.eq(is_in), status.fields.epno.eq(epno), status.fields.pend.eq(pending), status.fields.data.eq(have_data_stage), ] # Wire up the "SETUP" endpoint. self.comb += [ # Set the FIFO output to be the current buffer HEAD data.fields.data.eq(buf.dout), # Advance the FIFO when a byte is read buf.re.eq(data.we), If( usb_core.tok == PID.SETUP, buf.din.eq(data_recv_payload), buf.we.eq(data_recv_put), ), # Tie the trigger to the STATUS.HAVE bit trigger.eq(buf.readable & usb_core.setup), ] self.sync += [ # The 6th and 7th bytes of SETUP data are # the wLength field. If these are nonzero, # then there will be a Data stage following # this Setup stage. If( data_recv_put, If( data_byte == 0, epno.eq(usb_core.endp), is_in.eq(data_recv_payload[7]), ).Elif( data_byte == 6, If( data_recv_payload, have_data_stage.eq(1), ), ).Elif( data_byte == 7, If( data_recv_payload, have_data_stage.eq(1), ), ), data_byte.eq(data_byte + 1), ) ]